import { Inject, Injectable } from '@nestjs/common';
import { resolveApiSlowRequestThreshold } from '../../common/interceptors/api-timing.interceptor';
import { resolvePrismaSlowQueryThreshold } from '../../prisma/prisma-monitoring';
import { getOpsRuntimeEvents, redactText } from './ops-runtime.store';

export const OPS_PRISMA = 'OPS_PRISMA';
export const OPS_VIEW_PERMISSION = 'ops:view';

type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed';

interface BackgroundJobRow {
  id: string;
  type: string;
  status: string;
  attempts?: number | null;
  maxAttempts?: number | null;
  availableAt?: Date | string | null;
  lockedUntil?: Date | string | null;
  lastError?: string | null;
  updatedAt?: Date | string | null;
}

@Injectable()
export class OpsService {
  constructor(@Inject(OPS_PRISMA) private readonly prisma: any) {}

  async getRuntimeSnapshot(now = new Date()) {
    const events = getOpsRuntimeEvents();
    const database = { ok: true, error: undefined as string | undefined };
    let jobRows: BackgroundJobRow[] = [];
    let dirtySummaryCount = 0;

    try {
      [jobRows, dirtySummaryCount] = await Promise.all([
        this.prisma.backgroundJob.findMany({
          select: {
            id: true,
            type: true,
            status: true,
            attempts: true,
            maxAttempts: true,
            availableAt: true,
            lockedUntil: true,
            lastError: true,
            updatedAt: true,
          },
          orderBy: { updatedAt: 'desc' },
          take: 200,
        }),
        this.prisma.xiaobaoRiskSummary.count({ where: { dirty: true } }),
      ]);
    } catch (error) {
      database.ok = false;
      database.error = redactText(error instanceof Error ? error.message : String(error), 300);
    }

    return {
      collectedAt: now.toISOString(),
      thresholds: {
        apiSlowRequestMs: resolveApiSlowRequestThreshold(),
        prismaSlowQueryMs: resolvePrismaSlowQueryThreshold(),
      },
      database,
      slowRequests: events.slowRequests,
      slowQueries: events.slowQueries,
      jobQueue: buildJobQueue(jobRows),
      dirtySummaryCount,
      access: {
        requiredPermission: OPS_VIEW_PERMISSION,
        backendEnforced: false,
        adapter: 'OpsPermissionAdapter',
      },
    };
  }
}

function buildJobQueue(rows: BackgroundJobRow[]) {
  const totals = emptyStatusCounts();
  const byType = new Map<string, ReturnType<typeof emptyTypeCounts>>();
  const recentFailures = rows
    .filter((row) => normalizeStatus(row.status) === 'failed')
    .slice(0, 10)
    .map((row) => ({
      id: row.id,
      type: row.type,
      attempts: row.attempts ?? 0,
      maxAttempts: row.maxAttempts ?? 0,
      lastError: redactText(row.lastError ?? '', 300),
      updatedAt: toIso(row.updatedAt),
    }));

  for (const row of rows) {
    const status = normalizeStatus(row.status);
    totals[status] += 1;
    totals.total += 1;

    const current = byType.get(row.type) ?? emptyTypeCounts(row.type);
    current[status] += 1;
    current.total += 1;
    if (status === 'queued') {
      current.oldestQueuedAt = minIso(current.oldestQueuedAt, toIso(row.availableAt));
    }
    if (status === 'running') {
      current.nextLeaseExpiresAt = minIso(current.nextLeaseExpiresAt, toIso(row.lockedUntil));
    }
    byType.set(row.type, current);
  }

  return {
    totals,
    byType: Array.from(byType.values()).sort((a, b) => b.total - a.total || a.type.localeCompare(b.type)),
    recentFailures,
  };
}

function emptyStatusCounts(): Record<JobStatus, number> & { total: number } {
  return { queued: 0, running: 0, succeeded: 0, failed: 0, total: 0 };
}

function emptyTypeCounts(type: string) {
  return {
    type,
    queued: 0,
    running: 0,
    succeeded: 0,
    failed: 0,
    total: 0,
    oldestQueuedAt: undefined as string | undefined,
    nextLeaseExpiresAt: undefined as string | undefined,
  };
}

function normalizeStatus(status: string): JobStatus {
  return status === 'running' || status === 'succeeded' || status === 'failed' ? status : 'queued';
}

function toIso(value: Date | string | null | undefined): string | undefined {
  if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.toISOString() : undefined;
  if (typeof value !== 'string') return undefined;
  const date = new Date(value);
  return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
}

function minIso(current: string | undefined, next: string | undefined): string | undefined {
  if (!next) return current;
  if (!current) return next;
  return next < current ? next : current;
}
