import { Injectable } from '@nestjs/common';
import { DingtalkYidaService } from './sdk/dingtalk-yida.service';
import { DingtalkAttendanceService } from './sdk/dingtalk-attendance.service';
import {
  BUSINESS_TRIP_CHANGE_FIELDS,
  BUSINESS_TRIP_CHANGE_TYPES,
  BUSINESS_TRIP_FIELDS,
  FIELD_APPLICATION_FIELDS,
  FORM_UUIDS,
  OVERTIME_FIELDS,
} from './constants';
import { getDingtalkSerialNumber, formatTimeDisplay } from './utils';
import { SyncExecutionResult } from './sync/business-trip-sync.service';
import { SyncLogger } from './sync/sync-logger';

export interface ApprovalCancellationRecord {
  userId: string;
  approveId: string;
  creator: string;
  date: string | null;
  instanceIds: string[];
  serialNumbers: string[];
  taskCode: 'DINGTALK_BUSINESS_TRIP' | 'DINGTALK_FIELD_APPLICATION' | 'DINGTALK_OVERTIME';
}

@Injectable()
export class DingtalkRepairService {
  private static readonly MAX_LOOKBACK_DAYS = 60;
  constructor(
    private readonly yidaService: DingtalkYidaService,
    private readonly attendanceService: DingtalkAttendanceService,
  ) {}

  async cancelApprovals(params: {
    taskCode: 'DINGTALK_BUSINESS_TRIP' | 'DINGTALK_FIELD_APPLICATION' | 'DINGTALK_OVERTIME';
    userId: string;
    approveId: string;
    fromTime?: string;
    toTime?: string;
    dryRun?: boolean;
  }): Promise<SyncExecutionResult> {
    const logger = new SyncLogger('DingtalkRepairService');
    const startTime = Date.now();
    const errors: string[] = [];
    const pattern = (params.approveId || '').trim();
    const userId = params.userId.trim();
    const dryRun = params.dryRun !== false;

    logger.log('----开始审批撤销修复----');
    logger.log(`任务类型: ${params.taskCode}`);
    logger.log(`员工: ${userId}`);
    logger.log(`审批号: ${pattern}`);
    logger.log(`执行模式: ${dryRun ? '预览' : '实际撤销'}`);

    if (!pattern) {
      errors.push('审批号不能为空');
      return {
        success: false,
        processedCount: 0,
        errors,
        duration: Date.now() - startTime,
        logs: logger.getText(),
      };
    }

    const range = this.resolveScanRange(params.fromTime, params.toTime);
    const { displayFrom, displayTo } = formatTimeDisplay(range.fromTime, range.toTime);
    logger.log(`处理区间: ${displayFrom} ~ ${displayTo}`);
    if (range.defaulted) {
      logger.log(`未指定或超出时间范围，自动限制为最近 ${DingtalkRepairService.MAX_LOOKBACK_DAYS} 天`);
    }

    let records: ApprovalCancellationRecord[] = [];

    try {
      records = await this.scanApprovalRecords({
        ...params,
        userId,
        approveId: pattern,
        fromTime: range.fromTime,
        toTime: range.toTime,
      }, logger);
    } catch (error: any) {
      errors.push(`扫描撤销目标失败: ${error.message}`);
      logger.error(`扫描撤销目标失败: ${error.message}`);
      return {
        success: false,
        processedCount: 0,
        errors,
        duration: Date.now() - startTime,
        logs: logger.getText(),
      };
    }

    logger.log(`匹配到 ${records.length} 条审批记录`);

    if (dryRun) {
      return {
        success: errors.length === 0,
        processedCount: records.length,
        errors,
        duration: Date.now() - startTime,
        logs: logger.getText(),
        details: { records },
      };
    }

    const results: Array<ApprovalCancellationRecord & { status: 'CANCELLED' | 'FAILED' | 'ERROR'; response?: any; error?: string }> = [];
    let successCount = 0;

    for (const record of records) {
      try {
        const resp = await this.attendanceService.approveCancel(record.userId, record.approveId);
        if (resp.errcode === 0) {
          logger.log(`撤销成功: ${record.userId} & ${record.approveId}`);
          successCount++;
          results.push({ ...record, status: 'CANCELLED', response: resp });
        } else {
          const message = `撤销失败: ${record.userId} & ${record.approveId} errcode=${resp.errcode} errmsg=${resp.errmsg}`;
          errors.push(message);
          logger.error(message);
          results.push({ ...record, status: 'FAILED', response: resp });
        }
      } catch (error: any) {
        const message = `撤销异常: ${record.userId} & ${record.approveId} ${error.message}`;
        errors.push(message);
        logger.error(message);
        results.push({ ...record, status: 'ERROR', error: error.message });
      }
    }

    logger.log(`----审批撤销修复完成，成功 ${successCount} 条，失败 ${records.length - successCount} 条----`);

    return {
      success: errors.length === 0,
      processedCount: successCount,
      errors,
      duration: Date.now() - startTime,
      logs: logger.getText(),
      details: { records: results },
    };
  }

  private async scanApprovalRecords(
    params: {
      taskCode: 'DINGTALK_BUSINESS_TRIP' | 'DINGTALK_FIELD_APPLICATION' | 'DINGTALK_OVERTIME';
      userId: string;
      approveId: string;
      fromTime?: string;
      toTime?: string;
    },
    logger: SyncLogger,
  ): Promise<ApprovalCancellationRecord[]> {
    const config = this.getTaskConfig(params.taskCode);
    const workDates = new Map<string, { userId: string; creator: string; date: string; instanceIds: string[]; serialNumbers: string[] }>();

    for (const formUuid of config.formUuids) {
      const items = await this.yidaService.searchApprovedInstances(formUuid, params.fromTime, params.toTime, params.userId);
      logger.log(`扫描 ${formUuid} 命中 ${items.length} 条审批通过单据`);

      for (const item of items) {
        const serialNumber = getDingtalkSerialNumber(item);
        const creator = config.getCreator(item);
        const instanceId = item.formInstanceId;
        const segments = config.getSegments(item);

        for (const segment of segments) {
          for (const date of this.listCoveredDates(segment.fromTimestamp, segment.toTimestamp)) {
            const key = `${params.userId}|${date}`;
            const existing = workDates.get(key);
            if (existing) {
              if (!existing.instanceIds.includes(instanceId)) existing.instanceIds.push(instanceId);
              if (serialNumber && !existing.serialNumbers.includes(serialNumber)) existing.serialNumbers.push(serialNumber);
              continue;
            }
            workDates.set(key, {
              userId: params.userId,
              creator,
              date,
              instanceIds: [instanceId],
              serialNumbers: serialNumber ? [serialNumber] : [],
            });
          }
        }
      }
    }

    const matched: ApprovalCancellationRecord[] = [];
    for (const workDate of workDates.values()) {
      const resp = await this.attendanceService.getApproveRecords(workDate.userId, workDate.date);
      if (resp.errcode !== 0) {
        logger.error(`查询考勤审批失败: ${workDate.userId} ${workDate.date} errcode=${resp.errcode}`);
        continue;
      }

      for (const approval of resp.result?.approve_list || []) {
        const approveId = this.getAttendanceApproveId(approval);
        if (!approveId) continue;
        if (approval.tag_name !== config.tagName) continue;
        if (!this.matchesApproveId(approveId, params.approveId)) continue;

        matched.push({
          userId: workDate.userId,
          approveId,
          creator: workDate.creator,
          date: workDate.date,
          instanceIds: workDate.instanceIds,
          serialNumbers: workDate.serialNumbers,
          taskCode: params.taskCode,
        });
      }
    }

    const unique = new Map<string, ApprovalCancellationRecord>();
    for (const record of matched) {
      const key = `${record.userId}|${record.approveId}`;
      const existing = unique.get(key);
      if (existing) {
        for (const instanceId of record.instanceIds) {
          if (!existing.instanceIds.includes(instanceId)) existing.instanceIds.push(instanceId);
        }
        for (const serialNumber of record.serialNumbers) {
          if (!existing.serialNumbers.includes(serialNumber)) existing.serialNumbers.push(serialNumber);
        }
        continue;
      }
      unique.set(key, record);
    }

    return [...unique.values()];
  }

  private getTaskConfig(taskCode: 'DINGTALK_BUSINESS_TRIP' | 'DINGTALK_FIELD_APPLICATION' | 'DINGTALK_OVERTIME') {
    if (taskCode === 'DINGTALK_FIELD_APPLICATION') {
      return {
        formUuids: [FORM_UUIDS.FIELD_APPLICATION],
        tagName: '外出',
        getCreator: (item: any) => item.formData?.[FIELD_APPLICATION_FIELDS.CREATOR] || item.creatorUserId || 'unknown',
        getSegments: (item: any) => (item.formData?.[FIELD_APPLICATION_FIELDS.TABLE_FIELD] || [])
          .filter((segment: any) => segment?.[FIELD_APPLICATION_FIELDS.FROM_DATE] && segment?.[FIELD_APPLICATION_FIELDS.TO_DATE])
          .map((segment: any) => ({
            fromTimestamp: segment[FIELD_APPLICATION_FIELDS.FROM_DATE],
            toTimestamp: segment[FIELD_APPLICATION_FIELDS.TO_DATE],
          })),
      };
    }

    if (taskCode === 'DINGTALK_OVERTIME') {
      return {
        formUuids: [FORM_UUIDS.OVERTIME_APPLICATION],
        tagName: '加班',
        getCreator: (item: any) => item.formData?.[OVERTIME_FIELDS.CREATOR] || item.creatorUserId || 'unknown',
        getSegments: (item: any) => (item.formData?.[OVERTIME_FIELDS.TABLE_FIELD] || [])
          .filter((segment: any) => segment?.[OVERTIME_FIELDS.FROM_DATE] && segment?.[OVERTIME_FIELDS.TO_DATE])
          .map((segment: any) => ({
            fromTimestamp: segment[OVERTIME_FIELDS.FROM_DATE],
            toTimestamp: segment[OVERTIME_FIELDS.TO_DATE],
          })),
      };
    }

    return {
      formUuids: [FORM_UUIDS.BUSINESS_TRIP, FORM_UUIDS.BUSINESS_TRIP_CHANGE],
      tagName: '出差',
      getCreator: (item: any) => item.formData?.[BUSINESS_TRIP_FIELDS.CREATOR]
        || item.formData?.[BUSINESS_TRIP_CHANGE_FIELDS.CREATOR]
        || item.creatorUserId
        || 'unknown',
      getSegments: (item: any) => {
        const formData = item.formData || {};
        const changeType = formData[BUSINESS_TRIP_CHANGE_FIELDS.TYPE];
        if (
          changeType === BUSINESS_TRIP_CHANGE_TYPES.CANCEL_UNCHANGED
          || changeType === BUSINESS_TRIP_CHANGE_TYPES.CANCEL_CHANGED
        ) {
          return [];
        }

        return (formData[BUSINESS_TRIP_FIELDS.TABLE_FIELD] || formData[BUSINESS_TRIP_CHANGE_FIELDS.TABLE_FIELD] || [])
          .filter((segment: any) => segment?.[BUSINESS_TRIP_FIELDS.FROM_DATE] && segment?.[BUSINESS_TRIP_FIELDS.TO_DATE])
          .map((segment: any) => ({
            fromTimestamp: segment[BUSINESS_TRIP_FIELDS.FROM_DATE],
            toTimestamp: segment[BUSINESS_TRIP_FIELDS.TO_DATE],
          }));
      },
    };
  }

  private matchesApproveId(approveId: string, pattern: string): boolean {
    if (!pattern.includes('*')) return approveId === pattern;
    const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
    return new RegExp(`^${escaped}$`).test(approveId);
  }

  private getAttendanceApproveId(item: any): string {
    return item?.procInst_id || item?.approve_id || item?.approveId || '';
  }

  private resolveScanRange(fromTime?: string, toTime?: string): { fromTime: string; toTime: string; defaulted: boolean } {
    const end = this.parseDateOnly(toTime) || new Date();
    const maxStart = new Date(end);
    maxStart.setDate(maxStart.getDate() - (DingtalkRepairService.MAX_LOOKBACK_DAYS - 1));

    const requestedStart = this.parseDateOnly(fromTime);
    if (!requestedStart) {
      return {
        fromTime: this.formatDateCST(maxStart.getTime()),
        toTime: toTime || this.formatDateCST(end.getTime()),
        defaulted: true,
      };
    }

    if (requestedStart.getTime() < maxStart.getTime()) {
      return {
        fromTime: this.formatDateCST(maxStart.getTime()),
        toTime: toTime || this.formatDateCST(end.getTime()),
        defaulted: true,
      };
    }

    return {
      fromTime: this.formatDateCST(requestedStart.getTime()),
      toTime: toTime || this.formatDateCST(end.getTime()),
      defaulted: false,
    };
  }

  private parseDateOnly(value?: string): Date | null {
    if (!value) return null;
    const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
    if (!match) return null;
    const [, year, month, day] = match;
    return new Date(Number(year), Number(month) - 1, Number(day));
  }

  private listCoveredDates(fromTimestamp: number, toTimestamp: number): string[] {
    const result: string[] = [];
    const current = new Date(fromTimestamp);
    const end = new Date(toTimestamp);

    while (current.getTime() <= end.getTime()) {
      result.push(this.formatDateCST(current.getTime()));
      current.setUTCDate(current.getUTCDate() + 1);
      current.setUTCHours(0, 0, 0, 0);
    }

    return [...new Set(result)];
  }

  private formatDateCST(timestamp: number): string {
    return new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Shanghai' }).format(new Date(timestamp));
  }
}
