import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { AdpAuthService } from './sdk/adp-auth.service';
import { AdpLinkerService } from './sync/adp-linker.service';
import { AdpPtoSyncService } from './sync/adp-pto-sync.service';
import { AdpSyncExecutionResult } from './sync/adp-sync-result';

export const ADP_TASK_CODES = {
  LINKER: 'ADP_LINKER',
  PTO_SYNC: 'ADP_PTO_SYNC',
} as const;

/**
 * ADP 同步任务调度器。
 * 模式参考 organization/dingtalk/dingtalk-scheduler.service.ts:executeTask
 *
 * - linker：每天 02:00（员工进出不频繁）
 * - pto sync：每天 02:30（窗口 -7d ~ +35d）
 */
@Injectable()
export class AdpSchedulerService implements OnModuleInit {
  private readonly logger = new Logger(AdpSchedulerService.name);
  private readonly runningTasks = new Set<string>();

  constructor(
    private readonly prisma: PrismaService,
    private readonly auth: AdpAuthService,
    private readonly linker: AdpLinkerService,
    private readonly ptoSync: AdpPtoSyncService,
  ) {}

  /**
   * App 启动时确保两个 ADP 任务记录存在。
   * 之前是 lazy register（首次执行时才插入），导致 admin UI 在首次同步前看不到卡片。
   * 现在 upsert 让卡片始终可见，"立即触发"按钮始终可用。
   */
  async onModuleInit() {
    for (const code of [ADP_TASK_CODES.LINKER, ADP_TASK_CODES.PTO_SYNC]) {
      try {
        await this.prisma.automationTask.upsert({
          where: { code },
          create: {
            code,
            name: this.taskDisplayName(code),
            type: 'ADP_SYNC',
            scheduleType: 'CRON',
            status: 'ACTIVE',
          },
          update: {}, // 已存在不动，避免覆盖运营手动改的字段
        });
      } catch (e: any) {
        this.logger.warn(`ADP 任务 ${code} 注册失败（不影响启动）: ${e.message}`);
      }
    }
  }

  // ------------ Cron triggers ------------

  @Cron('0 0 2 * * *', { name: 'adp-linker', timeZone: 'America/Los_Angeles' })
  async handleLinkerCron() {
    await this.executeTask(ADP_TASK_CODES.LINKER, () => this.linker.run());
  }

  @Cron('0 30 2 * * *', { name: 'adp-pto-sync', timeZone: 'America/Los_Angeles' })
  async handlePtoSyncCron() {
    await this.executeTask(ADP_TASK_CODES.PTO_SYNC, () => this.ptoSync.run());
  }

  // ------------ Manual triggers (供 controller 调用) ------------

  /**
   * 异步触发任务：立即返回 accepted=true，实际执行在 setImmediate 后台跑。
   * 用途：HTTP 触发时避免前端等待 1+ 分钟超时；前端拿到响应后轮询 GET /status。
   * 已有正在执行的同一任务时返回 already-running，不重复启动。
   */
  triggerLinkerAsync(triggeredBy?: string, dryRun = false): { accepted: boolean; reason?: string } {
    return this.triggerAsync(ADP_TASK_CODES.LINKER, () => this.linker.run(dryRun), triggeredBy);
  }

  triggerPtoSyncAsync(
    triggeredBy?: string,
    windowStartDays?: number,
    windowEndDays?: number,
  ): { accepted: boolean; reason?: string } {
    return this.triggerAsync(
      ADP_TASK_CODES.PTO_SYNC,
      () => this.ptoSync.run(windowStartDays, windowEndDays),
      triggeredBy,
    );
  }

  private triggerAsync(
    taskCode: string,
    syncFn: () => Promise<AdpSyncExecutionResult>,
    triggeredBy?: string,
  ): { accepted: boolean; reason?: string } {
    if (this.runningTasks.has(taskCode)) {
      return { accepted: false, reason: 'already-running' };
    }
    setImmediate(() => {
      this.executeTask(taskCode, syncFn, 'MANUAL', triggeredBy).catch((e) =>
        this.logger.error(`异步任务 ${taskCode} 失败: ${e.message}`),
      );
    });
    return { accepted: true };
  }

  // ------------ executeTask（参考 dingtalk 模板）------------

  private async executeTask(
    taskCode: string,
    syncFn: () => Promise<AdpSyncExecutionResult>,
    triggerType: 'SCHEDULED' | 'MANUAL' = 'SCHEDULED',
    triggeredBy?: string,
  ): Promise<AdpSyncExecutionResult> {
    // 并发控制
    if (this.runningTasks.has(taskCode)) {
      const msg = `任务 ${taskCode} 正在执行中，跳过`;
      this.logger.warn(msg);
      return { success: true, duration: 0, errors: [msg] };
    }
    this.runningTasks.add(taskCode);

    try {
      // 调度触发时检查 enabled 开关；手动触发跳过
      if (triggerType !== 'MANUAL' && !this.auth.isEnabled) {
        this.logger.debug(`ADP 同步未启用，跳过 ${taskCode}`);
        return { success: true, duration: 0, errors: [] };
      }

      // upsert AutomationTask
      let task = await this.prisma.automationTask.findFirst({ where: { code: taskCode } });
      if (!task) {
        this.logger.log(`任务 ${taskCode} 自动注册到 platform_automation`);
        task = await this.prisma.automationTask.create({
          data: {
            code: taskCode,
            name: this.taskDisplayName(taskCode),
            type: 'ADP_SYNC',
            scheduleType: 'CRON',
            status: 'ACTIVE',
          },
        });
      } else if (task.status !== 'ACTIVE' && triggerType !== 'MANUAL') {
        this.logger.debug(`任务 ${taskCode} 未激活`);
        return { success: true, duration: 0, errors: [] };
      }

      // 创建 RUNNING 执行记录
      const execution = await this.prisma.automationExecution.create({
        data: {
          taskId: task.id,
          status: 'RUNNING',
          triggerType,
          triggeredBy,
        },
      });

      const startedAt = Date.now();
      try {
        const result = await syncFn();
        const success = result.success;

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

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

        return result;
      } catch (error: any) {
        this.logger.error(`任务 ${taskCode} 异常: ${error.message}`);
        await this.prisma.automationExecution.update({
          where: { id: execution.id },
          data: {
            status: 'FAILED',
            completedAt: new Date(),
            duration: Date.now() - startedAt,
            error: error.message,
          },
        });
        await this.prisma.automationTask.update({
          where: { id: task.id },
          data: {
            lastRunAt: new Date(),
            lastStatus: 'FAILED',
            totalRuns: { increment: 1 },
            failedRuns: { increment: 1 },
          },
        });
        return {
          success: false,
          duration: Date.now() - startedAt,
          errors: [error.message],
        };
      }
    } finally {
      this.runningTasks.delete(taskCode);
    }
  }

  private taskDisplayName(code: string): string {
    switch (code) {
      case ADP_TASK_CODES.LINKER:
        return 'ADP 员工 ID 绑定';
      case ADP_TASK_CODES.PTO_SYNC:
        return 'ADP PTO 时段同步';
      default:
        return code;
    }
  }

  private toJsonResult(result: AdpSyncExecutionResult): Record<string, unknown> {
    return {
      success: result.success,
      duration: result.duration,
      recordsFetched: result.recordsFetched,
      recordsUpserted: result.recordsUpserted,
      recordsDeleted: result.recordsDeleted,
      recordsUnmatched: result.recordsUnmatched,
      errors: result.errors,
      ...(result.extras ?? {}),
    };
  }

  /** 给 controller 用的状态查询 */
  async getStatus() {
    const tasks = await this.prisma.automationTask.findMany({
      where: { code: { in: [ADP_TASK_CODES.LINKER, ADP_TASK_CODES.PTO_SYNC] } },
      orderBy: { code: 'asc' },
    });

    const enriched = await Promise.all(
      tasks.map(async (task) => {
        const lastExec = await this.prisma.automationExecution.findFirst({
          where: { taskId: task.id },
          orderBy: { startedAt: 'desc' },
        });
        return {
          code: task.code,
          name: task.name,
          status: task.status,
          lastRunAt: task.lastRunAt,
          lastStatus: task.lastStatus,
          totalRuns: task.totalRuns,
          successRuns: task.successRuns,
          failedRuns: task.failedRuns,
          lastExecution: lastExec
            ? {
                id: lastExec.id,
                status: lastExec.status,
                startedAt: lastExec.startedAt,
                completedAt: lastExec.completedAt,
                duration: lastExec.duration,
                result: lastExec.result,
                logs: lastExec.logs,
                error: lastExec.error,
              }
            : null,
        };
      }),
    );

    return { tasks: enriched, isEnabled: this.auth.isEnabled, configValidation: this.auth.validateConfig() };
  }
}
