import { Injectable, Logger, BadRequestException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { IntegrationConfig, IntegrationConfigDetail } from '../entities/integration-config.entity';
import {
  ThirdConfigBaseDTO,
  DingTalkThirdConfigDto,
  WeComThirdConfigDto,
  IntegrationType,
  SaveIntegrationConfigDto,
} from './dto/config.dto';

@Injectable()
export class IntegrationConfigService {
  private readonly logger = new Logger(IntegrationConfigService.name);

  constructor(
    @InjectRepository(IntegrationConfig)
    private readonly configRepo: Repository<IntegrationConfig>,
    @InjectRepository(IntegrationConfigDetail)
    private readonly detailRepo: Repository<IntegrationConfigDetail>,
  ) {}

  /** 获取或创建主配置（全局单例） */
  private async ensureConfig(): Promise<IntegrationConfig> {
    let config = await this.configRepo.findOne({ where: { type: 'THIRD' } });
    if (!config) {
      config = this.configRepo.create({ type: 'THIRD', isSync: false });
      await this.configRepo.save(config);
    }
    return config;
  }

  /** DINGTALK -> DINGTALK_SYNC, WECOM -> WECOM_SYNC */
  private getDetailType(type: string): string {
    switch (type.toUpperCase()) {
      case 'WECOM':
        return 'WECOM_SYNC';
      case 'DINGTALK':
        return 'DINGTALK_SYNC';
      default:
        throw new BadRequestException(`不支持的第三方类型: ${type}`);
    }
  }

  /** 获取所有配置（脱敏，不返回 appSecret） */
  async getThirdConfig(): Promise<ThirdConfigBaseDTO[]> {
    const config = await this.ensureConfig();
    const details = await this.detailRepo.find({ where: { configId: config.id } });
    return details.map((detail) => ({
      type: detail.type.includes('WECOM')
        ? 'WECOM'
        : detail.type.includes('DINGTALK')
          ? 'DINGTALK'
          : detail.type,
      verify: detail.enable,
      config: this.parseAndMaskConfig(detail.content),
    }));
  }

  /** 按类型获取单个配置（脱敏） */
  async getConfigByType(type: string): Promise<ThirdConfigBaseDTO | null> {
    const all = await this.getThirdConfig();
    return all.find((c) => c.type === type.toUpperCase()) || null;
  }

  /** 保存/更新配置 */
  async saveConfig(request: SaveIntegrationConfigDto): Promise<void> {
    const config = await this.ensureConfig();
    const detailType = this.getDetailType(request.type);

    let existingDetail = await this.detailRepo.findOne({
      where: { configId: config.id, type: detailType },
    });

    const finalConfig = { ...request.config } as Record<string, unknown>;

    // 更新时若前端未传 appSecret，则保留旧值
    if (existingDetail && existingDetail.content) {
      if (!finalConfig.appSecret) {
        try {
          const oldParsed = JSON.parse(existingDetail.content);
          const oldCfg = oldParsed.config || oldParsed;
          if (oldCfg.appSecret) finalConfig.appSecret = oldCfg.appSecret;
        } catch {
          // ignore
        }
      }
    } else if (!finalConfig.appSecret) {
      throw new BadRequestException('首次配置必须提供 AppSecret');
    }

    // 连通性验证
    const token = await this.getTokenForTest(request.type, finalConfig);
    const verified = !!token;

    const content = JSON.stringify({
      type: request.type,
      verify: verified,
      config: finalConfig,
    });

    if (existingDetail) {
      existingDetail.content = content;
      existingDetail.enable = verified;
      await this.detailRepo.save(existingDetail);
    } else {
      existingDetail = this.detailRepo.create({
        configId: config.id,
        name: '第三方设置',
        type: detailType,
        content,
        enable: verified,
      });
      await this.detailRepo.save(existingDetail);
    }
    this.logger.log(`第三方配置已保存: ${request.type}, 验证: ${verified}`);
  }

  /** 测试连接 */
  async testConnection(
    type: IntegrationType,
    config: DingTalkThirdConfigDto | WeComThirdConfigDto,
  ): Promise<boolean> {
    try {
      const finalConfig = { ...config } as Record<string, unknown>;
      if (!finalConfig.appSecret) {
        const savedConfig = await this.getRawConfig(type);
        if (savedConfig?.appSecret) finalConfig.appSecret = savedConfig.appSecret;
      }
      const token = await this.getTokenForTest(type, finalConfig);
      return !!token;
    } catch (e) {
      this.logger.error(`连接测试失败: ${(e as Error).message}`);
      return false;
    }
  }

  /** 读同步状态：某类型是否已同步过 */
  async getSyncStatus(type: string): Promise<boolean> {
    const config = await this.configRepo.findOne({ where: { type: 'THIRD' } });
    if (!config || !config.isSync) return false;
    return config.syncResource === type.toUpperCase();
  }

  /** 写同步状态 */
  async setSyncStatus(syncing: boolean, type?: string): Promise<void> {
    const config = await this.ensureConfig();
    config.isSync = syncing;
    if (type) config.syncResource = type.toUpperCase();
    await this.configRepo.save(config);
  }

  /**
   * 供同步逻辑使用：读原始（未脱敏）配置。
   * 返回 { agentId, appSecret, corpId, appId? } 或 null。
   */
  async getRawConfig(type: string): Promise<Record<string, any> | null> {
    const config = await this.ensureConfig();
    const detailType = this.getDetailType(type);
    const detail = await this.detailRepo.findOne({
      where: { configId: config.id, type: detailType },
    });
    if (!detail || !detail.content) return null;
    try {
      const parsed = JSON.parse(detail.content);
      return parsed.config || parsed;
    } catch {
      return null;
    }
  }

  /**
   * 供同步逻辑使用：拿一个可用 access_token（未脱敏配置直接用）。
   * 目前仅实现钉钉。
   */
  async getAccessToken(type: string): Promise<string> {
    const config = await this.getRawConfig(type);
    if (!config) throw new NotFoundException(`未配置 ${type} 平台信息`);
    const token = await this.getTokenForTest(type, config);
    if (!token) throw new BadRequestException(`获取 ${type} access_token 失败`);
    return token;
  }

  // ── 私有工具 ──

  /** 用给定配置获取 token（钉钉真实调用，企微暂返回 null） */
  private async getTokenForTest(
    type: string,
    config: Record<string, unknown>,
  ): Promise<string | null> {
    try {
      if (type.toUpperCase() === 'DINGTALK') {
        const appKey = String(config.agentId || '');
        const appSecret = String(config.appSecret || '');
        if (!appKey || !appSecret) return null;
        return await this.fetchDingTalkToken(appKey, appSecret);
      }
      // 企微暂不实现，返回 null
      return null;
    } catch {
      return null;
    }
  }

  /** 调钉钉新版接口拿 access_token */
  private async fetchDingTalkToken(appKey: string, appSecret: string): Promise<string | null> {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 10_000);
    try {
      const res = await fetch('https://api.dingtalk.com/v1.0/oauth2/accessToken', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ appKey, appSecret }),
        signal: controller.signal,
      });
      if (!res.ok) return null;
      const body = (await res.json()) as { accessToken?: string; expireIn?: number };
      return body.accessToken || null;
    } finally {
      clearTimeout(timeout);
    }
  }

  /** 解析并脱敏：删掉 appSecret 后返回 config 对象 */
  private parseAndMaskConfig(content: string | null): unknown {
    if (!content) return {};
    try {
      const parsed = JSON.parse(content);
      const source = parsed.config || parsed;
      if (!source || typeof source !== 'object' || Array.isArray(source)) return {};
      const { appSecret: _appSecret, ...masked } = source as Record<string, unknown>;
      return masked;
    } catch {
      return {};
    }
  }
}
