import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { DingtalkAuthService } from './sdk/dingtalk-auth.service';
import { BusinessTripSyncService } from './sync/business-trip-sync.service';
import { FieldApplicationSyncService } from './sync/field-application-sync.service';
import { OvertimeSyncService } from './sync/overtime-sync.service';
import { EmployeeInfoSyncService } from './sync/employee-info-sync.service';
import { AnnualLeaveSyncService } from './sync/annual-leave-sync.service';
import { LeaveExtensionSyncService } from './sync/leave-extension-sync.service';
import { LeaveReminderService } from './sync/leave-reminder.service';
import { PurchaseSapSyncService } from './sync/purchase-sap-sync.service';
import { EmployeeManagementService } from './employee-management.service';
import { AnnualLeaveInsightService } from './annual-leave-insight.service';
import { SyncExecutionResult } from './sync/business-trip-sync.service';
import { SyncLogger } from './sync/sync-logger';

@Injectable()
export class DingtalkSchedulerService {
  private readonly logger = new Logger(DingtalkSchedulerService.name);
  private static readonly MANUAL_MAX_LOOKBACK_DAYS = 60;
  private static readonly SCHEDULED_TASKS_WITH_TIME_RANGE = new Set([
    'DINGTALK_BUSINESS_TRIP',
    'DINGTALK_FIELD_APPLICATION',
    'DINGTALK_OVERTIME',
    'DINGTALK_LEAVE_EXTENSION',
    'DINGTALK_PURCHASE_SAP_PROD',
    'DINGTALK_PURCHASE_SAP_TEST',
  ]);
  private readonly runningTasks = new Set<string>();

  constructor(
    private prisma: PrismaService,
    private authService: DingtalkAuthService,
    private businessTripSync: BusinessTripSyncService,
    private fieldApplicationSync: FieldApplicationSyncService,
    private overtimeSync: OvertimeSyncService,
    private employeeInfoSync: EmployeeInfoSyncService,
    private annualLeaveSync: AnnualLeaveSyncService,
    private leaveExtensionSync: LeaveExtensionSyncService,
    private leaveReminderSync: LeaveReminderService,
    private employeeManagementService: EmployeeManagementService,
    private annualLeaveInsightService: AnnualLeaveInsightService,
    private purchaseSapSync: PurchaseSapSyncService,
  ) {}

  // 员工信息同步（新表）- 每天 01:11
  @Cron('0 11 1 * * *', { name: 'dingtalk-employee-info', timeZone: 'Asia/Shanghai' })
  async handleEmployeeInfoSync() {
    await this.executeTask('DINGTALK_EMPLOYEE_INFO', () => this.employeeInfoSync.syncNewTable());
  }

  // 员工信息同步（旧表）- 每天 02:11
  @Cron('0 11 2 * * *', { name: 'dingtalk-employee-info-old', timeZone: 'Asia/Shanghai' })
  async handleEmployeeInfoOldSync() {
    await this.executeTask('DINGTALK_EMPLOYEE_INFO_OLD', () => this.employeeInfoSync.syncOldTable());
  }

  // 员工信息同步（本地表）- 每 2 小时
  @Cron('0 0 */2 * * *', { name: 'dingtalk-employee-sync', timeZone: 'Asia/Shanghai' })
  async handleEmployeeSync() {
    this.logger.log('开始定时同步员工信息到本地表...');
    try {
      const result = await this.employeeManagementService.syncEmployeesFromDingtalk();
      this.logger.log(
        `员工本地表同步完成: 新增 ${result.created}, 更新 ${result.updated}, 标记离职 ${result.terminated}`,
      );
    } catch (error: any) {
      this.logger.error(`员工本地表同步失败: ${error.message}`);
    }
  }

  // 假期配额快照同步 - 每 2 小时（偏移 30 分钟，避开员工同步）
  @Cron('0 30 1,3,5,7,9,11,13,15,17,19,21,23 * * *', { name: 'dingtalk-quota-snapshot', timeZone: 'Asia/Shanghai' })
  async handleQuotaSnapshotSync() {
    this.logger.log('开始定时同步假期配额快照...');
    try {
      const result = await this.annualLeaveInsightService.refreshQuotaSnapshot();
      this.logger.log(`配额快照同步完成: ${result.recordCount} 条记录`);
    } catch (error: any) {
      this.logger.error(`配额快照同步失败: ${error.message}`);
    }
  }

  // 年假释放 - 每天 03:11
  @Cron('0 11 3 * * *', { name: 'dingtalk-annual-leave', timeZone: 'Asia/Shanghai' })
  async handleAnnualLeaveRelease() {
    await this.executeTask('DINGTALK_ANNUAL_LEAVE', () => this.annualLeaveSync.sync());
  }

  // 出差变更同步 - 每小时 :15 和 :45
  @Cron('0 15,45 * * * *', { name: 'dingtalk-business-trip', timeZone: 'Asia/Shanghai' })
  async handleBusinessTripSync() {
    const range = this.getScheduledHalfHourWindow();
    await this.executeTask('DINGTALK_BUSINESS_TRIP', () => this.businessTripSync.syncChanges(range.fromTime, range.toTime));
  }

  // 外勤申请同步 - 每小时 :15 和 :45
  @Cron('0 15,45 * * * *', { name: 'dingtalk-field-application', timeZone: 'Asia/Shanghai' })
  async handleFieldApplicationSync() {
    const range = this.getScheduledHalfHourWindow();
    await this.executeTask('DINGTALK_FIELD_APPLICATION', () => this.fieldApplicationSync.sync(range.fromTime, range.toTime));
  }

  // 加班确认同步 - 每小时 :15 和 :45
  @Cron('0 15,45 * * * *', { name: 'dingtalk-overtime', timeZone: 'Asia/Shanghai' })
  async handleOvertimeSync() {
    const range = this.getScheduledHalfHourWindow();
    await this.executeTask('DINGTALK_OVERTIME', () => this.overtimeSync.sync(range.fromTime, range.toTime));
  }

  // 假期延期处理 - 每小时 :15 和 :45
  @Cron('0 15,45 * * * *', { name: 'dingtalk-leave-extension', timeZone: 'Asia/Shanghai' })
  async handleLeaveExtension() {
    const range = this.getScheduledHalfHourWindow();
    await this.executeTask('DINGTALK_LEAVE_EXTENSION', () => this.leaveExtensionSync.sync(range.fromTime, range.toTime));
  }

  // 采购申请SAP同步（生产）- 每小时 :20 和 :50
  @Cron('0 20,50 * * * *', { name: 'dingtalk-purchase-sap-prod', timeZone: 'Asia/Shanghai' })
  async handlePurchaseSapSyncProd() {
    const range = this.getScheduledHalfHourWindow();
    await this.executeTask('DINGTALK_PURCHASE_SAP_PROD', () =>
      this.purchaseSapSync.sync(range.fromTime, range.toTime, undefined, undefined, 'production'));
  }

  // 假期到期提醒 - 每周五 00:01
  @Cron('0 1 0 * * 5', { name: 'dingtalk-leave-reminder', timeZone: 'Asia/Shanghai' })
  async handleLeaveReminder() {
    await this.executeTask('DINGTALK_LEAVE_REMINDER', () => this.leaveReminderSync.sync());
  }

  /**
   * 手动触发指定任务
   */
  async triggerTask(
    taskCode: string,
    fromTime?: string,
    toTime?: string,
    triggeredBy?: string,
    userId?: string,
    logger?: SyncLogger,
  ): Promise<SyncExecutionResult> {
    const normalizedRange = this.normalizeManualTimeRange(taskCode, fromTime, toTime, logger);

    const taskMap: Record<string, () => Promise<SyncExecutionResult>> = {
      DINGTALK_BUSINESS_TRIP: () => this.businessTripSync.syncChanges(normalizedRange.fromTime, normalizedRange.toTime, userId, logger),
      DINGTALK_FIELD_APPLICATION: () => this.fieldApplicationSync.sync(normalizedRange.fromTime, normalizedRange.toTime, userId, logger),
      DINGTALK_OVERTIME: () => this.overtimeSync.sync(normalizedRange.fromTime, normalizedRange.toTime, userId, logger),
      DINGTALK_EMPLOYEE_INFO: () => this.employeeInfoSync.syncNewTable(userId),
      DINGTALK_EMPLOYEE_INFO_OLD: () => this.employeeInfoSync.syncOldTable(userId),
      DINGTALK_ANNUAL_LEAVE: () => this.annualLeaveSync.sync(userId),
      DINGTALK_LEAVE_EXTENSION: () => this.leaveExtensionSync.sync(normalizedRange.fromTime, normalizedRange.toTime, userId),
      DINGTALK_LEAVE_REMINDER: () => this.leaveReminderSync.sync(),
      DINGTALK_PURCHASE_SAP_PROD: () => this.purchaseSapSync.sync(normalizedRange.fromTime, normalizedRange.toTime, userId, logger, 'production'),
      DINGTALK_PURCHASE_SAP_TEST: () => this.purchaseSapSync.sync(normalizedRange.fromTime, normalizedRange.toTime, userId, logger, 'test'),
    };

    const syncFn = taskMap[taskCode];
    if (!syncFn) {
      return {
        success: false,
        processedCount: 0,
        errors: [`未知任务: ${taskCode}`],
        duration: 0,
      };
    }

    return this.executeTask(taskCode, syncFn, 'MANUAL', triggeredBy);
  }

  async refreshAnnualLeavePlan(userId?: string, year?: number): Promise<SyncExecutionResult> {
    return this.annualLeaveSync.refreshPlan(userId, year);
  }

  async triggerAnnualLeaveRelease(userId?: string, year?: number): Promise<SyncExecutionResult> {
    return this.annualLeaveSync.sync(userId, year);
  }

  private normalizeManualTimeRange(
    taskCode: string,
    fromTime?: string,
    toTime?: string,
    logger?: SyncLogger,
  ): { fromTime?: string; toTime?: string } {
    const tasksWithTimeRange = new Set([
      'DINGTALK_BUSINESS_TRIP',
      'DINGTALK_FIELD_APPLICATION',
      'DINGTALK_OVERTIME',
      'DINGTALK_LEAVE_EXTENSION',
    ]);
    if (!tasksWithTimeRange.has(taskCode)) {
      return { fromTime, toTime };
    }

    const effectiveToDate = this.parseDateOnly(toTime) || this.parseDateOnly(this.formatDateCST(Date.now()))!;
    const effectiveTo = this.formatDateOnly(effectiveToDate);
    const maxFromDate = new Date(effectiveToDate);
    maxFromDate.setDate(maxFromDate.getDate() - (DingtalkSchedulerService.MANUAL_MAX_LOOKBACK_DAYS - 1));
    const maxFrom = this.formatDateOnly(maxFromDate);

    if (!fromTime) {
      logger?.log(`后端保护：未指定开始日期，自动限制为最近 ${DingtalkSchedulerService.MANUAL_MAX_LOOKBACK_DAYS} 天`);
      return { fromTime: maxFrom, toTime: toTime || effectiveTo };
    }

    const requestedFromDate = this.parseDateOnly(fromTime);
    if (requestedFromDate && requestedFromDate.getTime() < maxFromDate.getTime()) {
      logger?.log(`后端保护：开始日期早于最近 ${DingtalkSchedulerService.MANUAL_MAX_LOOKBACK_DAYS} 天，已自动收敛到 ${maxFrom}`);
      return { fromTime: maxFrom, toTime: toTime || effectiveTo };
    }

    return { fromTime, toTime };
  }

  private getScheduledHalfHourWindow(now = new Date()): { fromTime: string; toTime: string } {
    const current = this.toCstPseudoDate(now);
    const minute = current.getUTCMinutes();

    const end = new Date(current);
    if (minute >= 30) {
      end.setUTCMinutes(30, 0, 0);
    } else {
      end.setUTCMinutes(0, 0, 0);
    }

    const start = new Date(end);
    start.setUTCMinutes(start.getUTCMinutes() - 30);

    return {
      fromTime: this.formatDateTimeOnly(start),
      toTime: this.formatDateTimeOnly(end),
    };
  }

  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 formatDateOnly(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  }

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

  private toCstPseudoDate(date: Date): Date {
    const parts = new Intl.DateTimeFormat('sv-SE', {
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
    }).formatToParts(date);
    const get = (type: string) => Number(parts.find(p => p.type === type)?.value || '0');
    return new Date(Date.UTC(
      get('year'),
      get('month') - 1,
      get('day'),
      get('hour'),
      get('minute'),
      get('second'),
      0,
    ));
  }

  private formatDateTimeOnly(date: Date): string {
    const year = date.getUTCFullYear();
    const month = String(date.getUTCMonth() + 1).padStart(2, '0');
    const day = String(date.getUTCDate()).padStart(2, '0');
    const hour = String(date.getUTCHours()).padStart(2, '0');
    const minute = String(date.getUTCMinutes()).padStart(2, '0');
    const second = String(date.getUTCSeconds()).padStart(2, '0');
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
  }

  /**
   * 统一执行逻辑：检查启用 → 创建记录 → 执行 → 更新结果
   */
  private async executeTask(
    taskCode: string,
    syncFn: () => Promise<SyncExecutionResult>,
    triggerType = 'SCHEDULED',
    triggeredBy?: string,
  ): Promise<SyncExecutionResult> {
    // 并发控制：同一任务不允许同时运行
    if (this.runningTasks.has(taskCode)) {
      this.logger.warn(`任务 ${taskCode} 正在执行中，跳过本次触发`);
      return { success: true, processedCount: 0, errors: [`任务 ${taskCode} 正在执行中，跳过`], duration: 0 };
    }
    this.runningTasks.add(taskCode);

    // 手动触发跳过 enabled / status 检查（用户主动操作）
    if (triggerType !== 'MANUAL') {
      const enabled = await this.authService.getIsEnabled();
      if (!enabled) {
        this.runningTasks.delete(taskCode);
        this.logger.debug(`钉钉同步未启用，跳过任务: ${taskCode}`);
        return { success: true, processedCount: 0, errors: [], duration: 0 };
      }
    }

    // 查找或自动注册任务
    let task = await this.prisma.automationTask.findFirst({ where: { code: taskCode } });
    if (!task) {
      this.logger.log(`任务 ${taskCode} 未在数据库中注册，自动创建`);
      task = await this.prisma.automationTask.create({
        data: {
          code: taskCode,
          name: taskCode,
          type: 'DINGTALK_SYNC',
          scheduleType: 'CRON',
          status: 'ACTIVE',
        },
      });
    } else if (task.status !== 'ACTIVE' && triggerType !== 'MANUAL') {
      this.runningTasks.delete(taskCode);
      this.logger.debug(`任务 ${taskCode} 未激活，跳过`);
      return { success: true, processedCount: 0, errors: [], duration: 0 };
    }

    // 创建执行记录（仅当任务已注册时）
    const execution = task
      ? await this.prisma.automationExecution.create({
          data: {
            taskId: task.id,
            status: 'RUNNING',
            triggerType,
            triggeredBy,
          },
        })
      : null;

    const executionStartedAt = Date.now();

    try {
      const result = await syncFn();

      // 更新执行记录
      if (execution) {
        await this.prisma.automationExecution.update({
          where: { id: execution.id },
          data: {
            status: result.success ? 'SUCCESS' : 'FAILED',
            completedAt: new Date(),
            duration: result.duration,
            result: result as any,
            error: result.errors.length > 0 ? result.errors.join('\n') : null,
            logs: result.logs || null,
          },
        });
      }

      // 更新任务统计
      if (task) {
        await this.prisma.automationTask.update({
          where: { id: task.id },
          data: {
            lastRunAt: new Date(),
            lastStatus: result.success ? 'SUCCESS' : 'FAILED',
            totalRuns: { increment: 1 },
            ...(result.success
              ? { successRuns: { increment: 1 } }
              : { failedRuns: { increment: 1 } }),
          },
        });
      }

      return result;
    } catch (error: any) {
      this.logger.error(`任务 ${taskCode} 执行失败: ${error.message}`);

      if (execution) {
        await this.prisma.automationExecution.update({
          where: { id: execution.id },
          data: {
            status: 'FAILED',
            completedAt: new Date(),
            duration: Date.now() - executionStartedAt,
            error: error.message,
          },
        });
      }

      if (task) {
        await this.prisma.automationTask.update({
          where: { id: task.id },
          data: {
            lastRunAt: new Date(),
            lastStatus: 'FAILED',
            totalRuns: { increment: 1 },
            failedRuns: { increment: 1 },
          },
        });
      }

      return {
        success: false,
        processedCount: 0,
        errors: [error.message],
        duration: Date.now() - executionStartedAt,
      };
    } finally {
      this.runningTasks.delete(taskCode);
    }
  }
}
