import { Provide, Scope, ScopeEnum, Inject } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { NetaClawAgentChannelGroupEntity } from '../entity/agent_channel_group.js';
import { WeixinArchiveSyncService, type SyncResult } from './weixin_archive_sync.js';

@Provide()
@Scope(ScopeEnum.Singleton)
export class NetaClawAgentChannelGroupService {
  @InjectEntityModel(NetaClawAgentChannelGroupEntity)
  groupRepo: Repository<NetaClawAgentChannelGroupEntity>;

  @Inject() archiveSyncService: WeixinArchiveSyncService;

  async sync(groupId: number): Promise<SyncResult> {
    return this.archiveSyncService.syncGroup(groupId);
  }

  async addByName(
    channelId: number,
    roomName: string,
    triggerMode: 'at_mention' | 'all',
    opts: { boundAgentId?: number | null; replyIdentityOverride?: 'silent' | 'ai_prefix' | null } = {},
  ): Promise<{ id: number; created: boolean }> {
    const trimmed = (roomName || '').trim();
    if (!trimmed) throw new Error('roomName required');
    if (trimmed.length > 128) throw new Error('roomName too long');
    if (triggerMode !== 'at_mention' && triggerMode !== 'all') {
      throw new Error(`invalid triggerMode: ${triggerMode}`);
    }

    const existing = await this.groupRepo.findOne({
      where: { channelId, roomName: trimmed },
    });
    if (existing) {
      return { id: existing.id, created: false };
    }
    const now = new Date().toISOString();
    const entity = this.groupRepo.create({
      channelId,
      roomId: trimmed,   // 架构 C: roomId 直接用群名作为白名单 key
      roomName: trimmed,
      status: 1,
      triggerMode,
      boundAgentId: opts.boundAgentId ?? null,
      replyIdentityOverride: opts.replyIdentityOverride ?? null,
      firstSeenAt: now,
      lastSeenAt: now,
    });
    const saved = await this.groupRepo.save(entity);
    return { id: saved.id, created: true };
  }

  async toggle(id: number, status: 0 | 1): Promise<void> {
    if (status !== 0 && status !== 1) {
      throw new Error(`invalid status value: ${status}`);
    }
    await this.groupRepo.update({ id }, { status });
  }

  async updatePolicy(id: number, triggerMode: string, triggerPrefix?: string | null): Promise<void> {
    if (triggerMode === 'prefix') {
      throw new Error('prefix mode 已弃用,请改用 at_mention 或 all');
    }
    const validModes = new Set(['at_mention', 'all']);
    if (!validModes.has(triggerMode)) {
      throw new Error(`invalid triggerMode: ${triggerMode}`);
    }
    await this.groupRepo.update({ id }, { triggerMode, triggerPrefix: null });
  }

  async setBoundAgent(id: number, agentId: number | null): Promise<void> {
    if (!agentId) {
      throw new Error('boundAgentId required');
    }
    await this.groupRepo.update({ id }, { boundAgentId: agentId } as Partial<NetaClawAgentChannelGroupEntity>);
  }

  async setReplyIdentityOverride(id: number, value: 'silent' | 'ai_prefix' | null): Promise<void> {
    if (value === null) {
      throw new Error('replyIdentityOverride required');
    }
    if (value !== null && value !== 'silent' && value !== 'ai_prefix') {
      throw new Error(`invalid replyIdentityOverride: ${value}`);
    }
    await this.groupRepo.update({ id }, { replyIdentityOverride: value } as Partial<NetaClawAgentChannelGroupEntity>);
  }

  async rename(id: number, roomName: string): Promise<void> {
    await this.groupRepo.update({ id }, { roomName });
  }

  async touchActive(channelId: number, roomId: string): Promise<void> {
    await this.groupRepo.update(
      { channelId, roomId },
      { lastActiveAt: new Date().toISOString() },
    );
  }

  async list(channelId: number): Promise<NetaClawAgentChannelGroupEntity[]> {
    return this.groupRepo.find({
      where: { channelId },
      order: { updateTime: 'DESC' as any },
    });
  }

  async cascadeDeleteByChannel(channelId: number): Promise<void> {
    await this.groupRepo.delete({ channelId });
  }

  async findByKey(channelId: number, roomId: string): Promise<NetaClawAgentChannelGroupEntity | null> {
    return this.groupRepo.findOne({ where: { channelId, roomId } });
  }

  async findById(id: number): Promise<NetaClawAgentChannelGroupEntity | null> {
    return this.groupRepo.findOne({ where: { id } });
  }

  async deleteByIds(ids: number[]): Promise<void> {
    if (!ids.length) return;
    await this.groupRepo.delete(ids);
  }

  async statsByChannels(channelIds: number[]): Promise<Map<number, { total: number; enabled: number }>> {
    const result = new Map<number, { total: number; enabled: number }>();
    if (channelIds.length === 0) return result;
    const rows = await this.groupRepo
      .createQueryBuilder('g')
      .select('g.channelId', 'channelId')
      .addSelect('COUNT(*)', 'total')
      .addSelect('SUM(CASE WHEN g.status = 1 THEN 1 ELSE 0 END)', 'enabled')
      .where('g.channelId IN (:...channelIds)', { channelIds })
      .groupBy('g.channelId')
      .getRawMany<{ channelId: string | number; total: string | number; enabled: string | number }>();
    for (const row of rows) {
      result.set(Number(row.channelId), {
        total: Number(row.total) || 0,
        enabled: Number(row.enabled) || 0,
      });
    }
    return result;
  }
}
