import { PostgresBackend } from '@agendajs/postgres-backend';
import {
  Injectable,
  Logger,
  OnApplicationBootstrap,
  OnApplicationShutdown,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Agenda, Job } from 'agenda';
import type { JobsQueryOptions } from 'agenda/dist/types/JobQuery.js';
import { PrismaService } from 'nestjs-prisma';

interface GetJobsParams {
  job?: string;
  state?: string;
  toggle?: string; // 'active' | 'paused'
  module?: string;
  q?: string;
  skip?: number;
  limit?: number;
}

@Injectable()
export class AgendaService
  implements OnApplicationBootstrap, OnApplicationShutdown
{
  private agenda: Agenda;
  private readonly logger = new Logger(AgendaService.name);
  private ready = false;

  constructor(
    private readonly configService: ConfigService,
    private readonly prisma: PrismaService,
  ) {
    const databaseUrl =
      process.env.DATABASE_URL ||
      this.configService.get<string>('prisma.DATABASE_URL');
    this.agenda = new Agenda({
      backend: new PostgresBackend({ connectionString: databaseUrl }),
      defaultConcurrency: 5,
      maxConcurrency: 20,
    });

    this.agenda.on('error', (err) => {
      this.logger.error('Agenda error', err);
    });
  }

  async onApplicationBootstrap() {
    await this.agenda.start();
    this.ready = true;
    this.logger.log('Agenda started');

    if (process.env.NODE_ENV === 'development') {
      await this.seedDevJobs();
    }
  }

  /** 开发环境注入 mock job，方便 UI 调试 */
  private async seedDevJobs() {
    const prefix = 'dev-mock:';

    // handler 每次启动都必须注册，否则重启后 agenda 找不到 handler
    this.define(
      `${prefix}sync-user-data`,
      this.withLog(async (_job, log) => {
        log('Fetching user data from upstream...');
        await new Promise((r) => setTimeout(r, 2000));
        log('Synced 128 users');
      }),
    );

    this.define(
      `${prefix}generate-daily-report`,
      this.withLog(async (_job, log) => {
        log('Generating daily report...');
        await new Promise((r) => setTimeout(r, 3000));
        log('Report generated: /reports/2026-05-18.pdf');
      }),
    );

    this.define(
      `${prefix}cleanup-expired-sessions`,
      this.withLog(async (_job, log) => {
        log('Cleaning up expired sessions...');
        await new Promise((r) => setTimeout(r, 1000));
        log('Removed 42 sessions');
      }),
    );

    this.define(
      `${prefix}send-notification-digest`,
      this.withLog(async (_job, log) => {
        log('Sending digest...');
        await new Promise((r) => setTimeout(r, 1500));
        log('Sent to 56 users');
      }),
    );

    this.define(
      `${prefix}check-health`,
      this.withLog(async (_job, log) => {
        log('Checking service health...');
        await new Promise((r) => setTimeout(r, 500));
        log('All services healthy');
      }),
    );

    // 只在首次启动时创建 job，避免重复
    const existing = await this.agenda.queryJobs({ search: prefix });
    if (existing.total > 0) return;

    // 创建 jobs：不同调度方式覆盖多种状态
    await this.every(
      '0 */6 * * *',
      `${prefix}sync-user-data`,
      {
        source: 'mock',
      },
      'user',
    );
    await this.every(
      '0 2 * * *',
      `${prefix}generate-daily-report`,
      {
        type: 'daily',
      },
      'report',
    );
    await this.every(
      '*/30 * * * *',
      `${prefix}cleanup-expired-sessions`,
      undefined,
      'system',
    );
    await this.schedule(
      'in 2 hours',
      `${prefix}send-notification-digest`,
      {
        channel: 'email',
      },
      'notification',
    );
    await this.now(`${prefix}check-health`, undefined, 'system');

    this.logger.log('Dev mock jobs seeded');
  }

  async onApplicationShutdown() {
    await this.agenda.stop();
    this.logger.log('Agenda stopped');
  }

  /**
   * 封装带日志的 job handler。
   * 自动记录执行开始/结束/耗时/过程日志到 t_job_log，写日志出错不影响 handler 本身的执行结果。
   *
   * 用法：
   *   agendaService.define('send-email', agendaService.withLog(async (job, log) => {
   *     log('开始发送邮件...');
   *     const result = await sendEmail();
   *     log(`发送完成，共 ${result.count} 封`);
   *   }));
   */
  withLog(
    handler: (job: Job, log: (message: string) => void) => Promise<void>,
  ): (job: Job) => Promise<void> {
    return async (job: Job) => {
      const startAt = new Date();
      const logs: string[] = [];
      const log = (message: string) => {
        const ts = new Date().toISOString();
        logs.push(`[${ts}] ${message}`);
      };
      let error: string | undefined;
      try {
        await handler(job, log);
      } catch (err) {
        error = err instanceof Error ? err.message : String(err);
        throw err; // 重新抛出，让 agenda 标记 failed
      } finally {
        const endAt = new Date();
        const durationMs = endAt.getTime() - startAt.getTime();
        this.prisma.jobLog
          .create({
            data: {
              jobName: job.attrs.name,
              status: error ? 'failed' : 'success',
              data: (job.attrs.data as any) ?? undefined,
              error: error ?? null,
              logs: logs.length > 0 ? logs.join('\n') : null,
              startAt,
              endAt,
              durationMs,
            },
          })
          .catch((e) => {
            this.logger.warn(
              `Failed to write job log for "${job.attrs.name}": ${e?.message}`,
            );
          });
      }
    };
  }

  /** 定义任务处理器 */
  define(
    name: string,
    handler: (job: Job) => Promise<void>,
    options?: {
      concurrency?: number;
      priority?: number;
      lockLifetime?: number;
    },
  ) {
    this.agenda.define(name, handler, options);
  }

  /** 将 module 合并到 data 中 */
  private mergeModule(
    data?: Record<string, any>,
    module?: string,
  ): Record<string, any> | undefined {
    if (!module) return data;
    return { ...data, _module: module };
  }

  /** 立即执行 */
  async now(name: string, data?: Record<string, any>, module?: string) {
    return this.agenda.now(name, this.mergeModule(data, module));
  }

  /** 延迟/定时执行（一次性） */
  async schedule(
    when: string | Date,
    name: string,
    data?: Record<string, any>,
    module?: string,
  ) {
    return this.agenda.schedule(when, name, this.mergeModule(data, module));
  }

  /** 周期执行 */
  async every(
    interval: string,
    name: string,
    data?: Record<string, any>,
    module?: string,
  ) {
    return this.agenda.every(interval, name, this.mergeModule(data, module));
  }

  /** 取消任务 */
  async cancel(query: Record<string, any>) {
    return this.agenda.cancel(query);
  }

  /** 查询任务（低级接口） */
  async queryJobs(query: Record<string, any>) {
    return this.agenda.queryJobs(query);
  }

  /** 返回已通过 define() 注册的 job 名称列表 */
  getDefinedJobNames(): string[] {
    return Object.keys((this.agenda as any).definitions ?? {});
  }

  // ─── UI management methods ────────────────────────────────────────────────

  /** 获取执行健康指标概览 */
  async getMetricsOverview() {
    const now = new Date();
    const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);

    const [totalAll, total24h, successAll, success24h, failedAll, failed24h] =
      await Promise.all([
        this.prisma.jobLog.count(),
        this.prisma.jobLog.count({ where: { startAt: { gte: last24h } } }),
        this.prisma.jobLog.count({ where: { status: 'success' } }),
        this.prisma.jobLog.count({
          where: { status: 'success', startAt: { gte: last24h } },
        }),
        this.prisma.jobLog.count({ where: { status: 'failed' } }),
        this.prisma.jobLog.count({
          where: { status: 'failed', startAt: { gte: last24h } },
        }),
      ]);

    // Success rate
    const denomAll = successAll + failedAll;
    const denom24h = success24h + failed24h;
    const successRateAll = denomAll > 0 ? (successAll / denomAll) * 100 : 0;
    const successRate24h = denom24h > 0 ? (success24h / denom24h) * 100 : 0;

    // Duration AVG/P95 via raw SQL (duration_ms is integer, result in ms)
    const durationAll = await this.prisma.$queryRaw<
      { avg: number | null; p95: number | null }[]
    >`
      SELECT
        AVG(duration_ms) as avg,
        PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration_ms) as p95
      FROM t_job_log
      WHERE status = 'success'
    `;

    const duration24h = await this.prisma.$queryRaw<
      { avg: number | null; p95: number | null }[]
    >`
      SELECT
        AVG(duration_ms) as avg,
        PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration_ms) as p95
      FROM t_job_log
      WHERE status = 'success' AND start_at >= ${last24h}
    `;

    // Running/Queued from Agenda queue
    let running = 0;
    let queued = 0;
    if (this.ready) {
      const result = await this.agenda.queryJobs({});
      for (const j of result.jobs) {
        if (j.state === 'running') running++;
        if (j.state === 'queued') queued++;
      }
    }

    return {
      totalRuns: { last24h: total24h, total: totalAll },
      successRate: {
        last24h: Math.round(successRate24h * 10) / 10,
        total: Math.round(successRateAll * 10) / 10,
      },
      failed: { last24h: failed24h, total: failedAll },
      duration: {
        last24h: {
          avg: Number(duration24h[0]?.avg) || 0,
          p95: Number(duration24h[0]?.p95) || 0,
        },
        total: {
          avg: Number(durationAll[0]?.avg) || 0,
          p95: Number(durationAll[0]?.p95) || 0,
        },
      },
      running,
      queued,
    };
  }

  /** 获取任务列表 + 各状态概览 */
  async getJobs(params: GetJobsParams) {
    if (!this.ready) {
      return {
        overview: {
          running: 0,
          scheduled: 0,
          queued: 0,
          completed: 0,
          failed: 0,
          repeating: 0,
          paused: 0,
        },
        jobs: [],
        total: 0,
        totalPages: 0,
      };
    }
    const { job, state, toggle, module, q, skip = 0, limit = 20 } = params;

    const allQuery: JobsQueryOptions = {};
    if (job) allQuery.name = job;
    if (q) allQuery.search = q;

    const allResult = await this.agenda.queryJobs(allQuery);

    // 应用层过滤 module：Agenda queryJobs API 仅支持 name/state/search，
    // 不支持按 data 内字段查询。Job 数量通常较少（几十~几百），应用层过滤足够。
    // 若将来 job 量大幅增长，可直接查 Agenda 底层 PostgreSQL 表用 JSONB 查询替代。
    const moduleFiltered = module
      ? allResult.jobs.filter(
          (j) => (j.data as Record<string, any>)?._module === module,
        )
      : allResult.jobs;

    const overview: Record<string, number> = {
      running: 0,
      scheduled: 0,
      queued: 0,
      completed: 0,
      failed: 0,
      repeating: 0,
      paused: 0,
      active: 0,
    };

    for (const j of moduleFiltered) {
      const s = j.state;
      if (s in overview) overview[s]++;
    }

    // 第一步：按 job 原始状态过滤（running/queued 等）
    const stateFiltered = state
      ? moduleFiltered.filter((j) => j.state === state)
      : moduleFiltered;

    // 从状态过滤后的数据计算 active/paused 数量
    for (const j of stateFiltered) {
      if (j.disabled) overview.paused++;
      else overview.active++;
    }

    // 第二步：按 active/paused 过滤
    const toggleFiltered = toggle
      ? toggle === 'active'
        ? stateFiltered.filter((j) => !j.disabled)
        : toggle === 'paused'
        ? stateFiltered.filter((j) => j.disabled)
        : stateFiltered
      : stateFiltered;

    const total = toggleFiltered.length;
    const paged = toggleFiltered.slice(skip, skip + limit);

    // 批量查询当前页 jobs 的历史执行统计（success/failed 来自 job log）
    const jobNames = paged.map((j) => j.name);
    const runStats = jobNames.length
      ? await this.prisma.jobLog.groupBy({
          by: ['jobName', 'status'],
          where: { jobName: { in: jobNames } },
          _count: true,
        })
      : [];

    const logMap = new Map<string, { success: number; failed: number }>();
    for (const row of runStats) {
      const entry = logMap.get(row.jobName) ?? { success: 0, failed: 0 };
      if (row.status === 'success') entry.success = row._count;
      else if (row.status === 'failed') entry.failed = row._count;
      logMap.set(row.jobName, entry);
    }

    // queued/running 数量：统计同名 job 中处于该状态的条数
    const queuedRunningMap = new Map<
      string,
      { queued: number; running: number }
    >();
    for (const j of allResult.jobs) {
      if (j.state !== 'queued' && j.state !== 'running') continue;
      const entry = queuedRunningMap.get(j.name) ?? {
        queued: 0,
        running: 0,
      };
      entry[j.state as 'queued' | 'running']++;
      queuedRunningMap.set(j.name, entry);
    }

    const jobs = paged.map((j) => {
      const rawData = (j.data as Record<string, any>) ?? null;
      const mod = rawData?._module ?? null;
      const log = logMap.get(j.name) ?? { success: 0, failed: 0 };
      const qr = queuedRunningMap.get(j.name) ?? { queued: 0, running: 0 };
      return {
        id: String(j._id),
        name: j.name,
        module: mod,
        state: j.state,
        runs: { queued: qr.queued, running: qr.running, ...log },
        nextRunAt: j.nextRunAt,
        lastRunAt: j.lastRunAt,
        lastFinishedAt: j.lastFinishedAt,
        failReason: (j as any).failReason ?? null,
        failedAt: j.failedAt ?? null,
        repeatInterval: j.repeatInterval ?? null,
        data: rawData,
        lockedAt: j.lockedAt ?? null,
        disabled: j.disabled ?? false,
      };
    });

    // 收集所有 distinct module（从未过滤的全量 jobs 中提取）
    const modules = [
      ...new Set(
        allResult.jobs
          .map((j) => (j.data as Record<string, any>)?._module)
          .filter(Boolean) as string[],
      ),
    ].sort();

    return {
      overview,
      modules,
      jobs,
      total,
      totalPages: Math.ceil(total / limit),
    };
  }

  /** Requeue jobs — reschedule them to run immediately */
  async requeueByIds(jobIds: string[]) {
    let affected = 0;
    for (const id of jobIds) {
      const result = await this.agenda.queryJobs({ id });
      if (result.jobs.length > 0) {
        const job = result.jobs[0];
        const liveJobs = await this.agenda.queryJobs({ id } as any);
        if (liveJobs.jobs.length > 0) {
          await this.agenda.cancel({ _id: id } as any);
          await this.agenda.now(job.name, job.data as Record<string, any>);
          affected++;
        }
      }
    }
    return { affected };
  }

  /** Retry failed jobs */
  async retryByIds(jobIds: string[]) {
    let affected = 0;
    for (const id of jobIds) {
      const result = await this.agenda.queryJobs({ id });
      if (result.jobs.length > 0) {
        const job = result.jobs[0];
        await this.agenda.cancel({ _id: id } as any);
        await this.agenda.now(job.name, job.data as Record<string, any>);
        affected++;
      }
    }
    return { affected };
  }

  /** Delete jobs by IDs */
  async deleteByIds(jobIds: string[]) {
    let affected = 0;
    for (const id of jobIds) {
      const count = await this.agenda.cancel({ _id: id } as any);
      affected += count ?? 0;
    }
    return { affected };
  }

  /** Disable (pause) jobs by IDs */
  async disableByIds(jobIds: string[]) {
    let affected = 0;
    for (const id of jobIds) {
      const count = await this.agenda.disable({ id });
      affected += count ?? 0;
    }
    return { affected };
  }

  /** Enable (resume) jobs by IDs */
  async enableByIds(jobIds: string[]) {
    let affected = 0;
    for (const id of jobIds) {
      const count = await this.agenda.enable({ id });
      affected += count ?? 0;
    }
    return { affected };
  }

  /** 查询某个 job 的执行历史 */
  async getJobLogs(params: {
    jobName: string;
    status?: string;
    before?: string;
    skip?: number;
    limit?: number;
  }) {
    const { jobName, status, before, skip = 0, limit = 20 } = params;
    const where: Record<string, any> = { jobName };
    if (status) where.status = status;
    if (before) where.startAt = { lte: new Date(before) };
    const [logs, total] = await Promise.all([
      this.prisma.jobLog.findMany({
        where,
        orderBy: { startAt: 'desc' },
        skip,
        take: limit,
      }),
      this.prisma.jobLog.count({ where }),
    ]);
    return { logs, total };
  }

  /** 获取单个 job 详情 + 运行统计 */
  async getJobDetail(jobName: string) {
    // 查询 agenda 中该 job
    const result = await this.agenda.queryJobs({ name: jobName });
    if (result.jobs.length === 0) return null;

    const j = result.jobs[0];
    const rawData = (j.data as Record<string, any>) ?? null;

    // queued/running 数量：同名 job 的状态统计
    let queued = 0;
    let running = 0;
    for (const job of result.jobs) {
      if (job.state === 'queued') queued++;
      if (job.state === 'running') running++;
    }

    // 从 t_job_log 聚合统计
    const [logStats, logCounts] = await Promise.all([
      this.prisma.jobLog.aggregate({
        where: { jobName },
        _max: { durationMs: true, startAt: true },
        _min: { durationMs: true, startAt: true },
        _avg: { durationMs: true },
        _count: true,
      }),
      this.prisma.jobLog.groupBy({
        by: ['status'],
        where: { jobName },
        _count: true,
      }),
    ]);

    let success = 0;
    let failed = 0;
    for (const row of logCounts) {
      if (row.status === 'success') success = row._count;
      else if (row.status === 'failed') failed = row._count;
    }

    return {
      name: j.name,
      module: rawData?._module ?? null,
      state: j.state,
      disabled: j.disabled ?? false,
      nextRunAt: j.nextRunAt,
      lastRunAt: j.lastRunAt,
      lastFinishedAt: j.lastFinishedAt,
      failReason: (j as any).failReason ?? null,
      repeatInterval: j.repeatInterval ?? null,
      data: rawData,
      runs: { queued, running, success, failed },
      duration: {
        totalRuns: logStats._count,
        maxMs: logStats._max.durationMs,
        minMs: logStats._min.durationMs,
        avgMs: logStats._avg.durationMs
          ? Math.round(logStats._avg.durationMs)
          : null,
        firstRunAt: logStats._min.startAt,
        lastRunAt: logStats._max.startAt,
      },
    };
  }
}
