import { randomUUID } from "node:crypto";
import type { AppDatabase } from "../db/database.js";
import { envNumber } from "../env.js";

export type WebhookRunStatus = "success" | "error";

export type WebhookRunRecord = {
  id: string;
  webhook_id: string;
  user_id: string;
  session_id: string;
  status: WebhookRunStatus;
  request_payload: string | null;
  response_status: number | null;
  attempts: number;
  created_at: string;
};

export const createWebhookAuditRepository = (db: AppDatabase) => ({
  create(input: {
    webhookId: string;
    userId: string;
    sessionId: string;
    status: WebhookRunStatus;
    requestPayload?: unknown;
    responseStatus?: number | null;
    attempts?: number;
    createdAt?: string;
  }) {
    const run = {
      id: randomUUID(),
      webhook_id: input.webhookId,
      user_id: input.userId,
      session_id: input.sessionId,
      status: input.status,
      request_payload: input.requestPayload ? JSON.stringify(input.requestPayload) : null,
      response_status: input.responseStatus ?? null,
      attempts: input.attempts ?? 1,
      created_at: input.createdAt ?? new Date().toISOString(),
    };

    db.prepare(
      `INSERT INTO webhook_runs (id, webhook_id, user_id, session_id, status, request_payload, response_status, attempts, created_at)
       VALUES (@id, @webhook_id, @user_id, @session_id, @status, @request_payload, @response_status, @attempts, @created_at)`,
    ).run(run);

    return run;
  },

  listForSession(userId: string, sessionId: string, limit = 20) {
    return db
      .prepare(
        `SELECT id, webhook_id, user_id, session_id, status, request_payload, response_status, attempts, created_at
         FROM webhook_runs
         WHERE session_id = ? AND user_id = ?
         ORDER BY created_at DESC
         LIMIT ?`,
      )
      .all(sessionId, userId, limit) as WebhookRunRecord[];
  },

  purgeOlderThan(isoCutoff: string): number {
    const result = db
      .prepare(`DELETE FROM webhook_runs WHERE created_at < ?`)
      .run(isoCutoff);
    return Number(result.changes ?? 0);
  },

  enforcePerUserCap(maxPerUser: number): number {
    if (maxPerUser <= 0) return 0;
    // Uses SQLite window function (3.25+) to keep the most recent N rows
    // per user and delete the rest. user_id is always present in the table.
    const result = db
      .prepare(
        `DELETE FROM webhook_runs
         WHERE rowid IN (
           SELECT rowid FROM (
             SELECT rowid,
                    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, rowid DESC) AS rn
             FROM webhook_runs
           )
           WHERE rn > ?
         )`,
      )
      .run(maxPerUser);
    return Number(result.changes ?? 0);
  },

  /**
   * Per-webhook usage stats for a single user over a recent time window.
   * `isoSince` should be a UTC ISO string (e.g. now - 7 days).
   * Returns a map of webhook_id -> { runs, successes, successRate }.
   */
  usageForUserSince(isoSince: string, userId: string): Record<
    string,
    { runs: number; successes: number; successRate: number }
  > {
    const rows = db
      .prepare(
        `SELECT webhook_id,
                COUNT(*) AS runs,
                SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS successes
         FROM webhook_runs
         WHERE user_id = ? AND created_at >= ?
         GROUP BY webhook_id`,
      )
      .all(userId, isoSince) as Array<{
      webhook_id: string;
      runs: number;
      successes: number | null;
    }>;

    const out: Record<string, { runs: number; successes: number; successRate: number }> = {};
    for (const row of rows) {
      const runs = Number(row.runs ?? 0);
      const successes = Number(row.successes ?? 0);
      out[row.webhook_id] = {
        runs,
        successes,
        successRate: runs > 0 ? successes / runs : 0,
      };
    }
    return out;
  },
});

export type WebhookAuditPurgeConfig = {
  retentionDays: number;
  maxPerUser: number;
};

export const webhookAuditPurgeConfigFromEnv = (): WebhookAuditPurgeConfig => {
  const retentionDays = Math.max(0, envNumber(process.env.WEBHOOK_RUNS_RETENTION_DAYS, 30));
  const maxPerUser = Math.max(0, envNumber(process.env.WEBHOOK_RUNS_MAX_PER_USER, 1000));
  return { retentionDays, maxPerUser };
};

export type WebhookAuditPurgeReport = {
  deletedByAge: number;
  deletedByCap: number;
  cutoff: string | null;
};

/**
 * Run both purge passes (age + per-user cap) against the audit table.
 * Returns a small report for logging / health endpoints. Safe to call on
 * every boot and on a timer.
 */
export const runWebhookAuditPurge = (
  db: AppDatabase,
  config: WebhookAuditPurgeConfig = webhookAuditPurgeConfigFromEnv(),
): WebhookAuditPurgeReport => {
  const audit = createWebhookAuditRepository(db);
  let deletedByAge = 0;
  let cutoff: string | null = null;
  if (config.retentionDays > 0) {
    cutoff = new Date(Date.now() - config.retentionDays * 86_400_000).toISOString();
    deletedByAge = audit.purgeOlderThan(cutoff);
  }
  const deletedByCap = config.maxPerUser > 0 ? audit.enforcePerUserCap(config.maxPerUser) : 0;
  return { deletedByAge, deletedByCap, cutoff };
};
