import type { Request } from "express";
import { query } from "../db/pool.js";
import { recordAppEvent } from "./app-log.js";

export type PublicAuthRoute =
  | "auth.register"
  | "auth.login"
  | "auth.forgot-password"
  | "auth.reset-password"
  | "auth.verify-email";

interface ThrottleRow {
  throttle_key: string;
  route: string;
  ip_address: string;
  email_hint: string | null;
  risk_score: number;
  window_started_at: string;
  blocked_until: string | null;
  last_seen_at: string;
}

const WINDOW_MS = 10 * 60 * 1000;
const THROTTLE_MESSAGE = "Demasiados intentos. Espera un momento.";

function normalizeIp(ip: string | undefined | null): string {
  if (!ip) return "unknown";
  return ip.startsWith("::ffff:") ? ip.slice(7) : ip;
}

function normalizeEmail(email?: string | null): string | null {
  const value = email?.trim().toLowerCase();
  return value ? value : null;
}

function buildKey(route: PublicAuthRoute, ip: string, email?: string | null): string[] {
  const keys = [`${route}|ip:${normalizeIp(ip)}`];
  const normalizedEmail = normalizeEmail(email);
  if (normalizedEmail) {
    keys.push(`${route}|ip:${normalizeIp(ip)}|email:${normalizedEmail}`);
  }
  return keys;
}

function riskScoreToBlockMs(score: number): number {
  if (score >= 12) return 60 * 60 * 1000;
  if (score >= 8) return 30 * 60 * 1000;
  if (score >= 5) return 5 * 60 * 1000;
  if (score >= 3) return 60 * 1000;
  return 0;
}

function isFreshWindow(row: ThrottleRow): boolean {
  return Date.now() - new Date(row.window_started_at).getTime() > WINDOW_MS;
}

async function loadThrottleRows(keys: string[]): Promise<ThrottleRow[]> {
  if (keys.length === 0) return [];
  return query<ThrottleRow>(
    `SELECT throttle_key, route, ip_address, email_hint, risk_score, window_started_at, blocked_until, last_seen_at
     FROM auth_abuse_controls
     WHERE throttle_key = ANY($1::text[])`,
    [keys]
  );
}

export async function checkPublicAuthThrottle(route: PublicAuthRoute, ip: string, email?: string | null): Promise<{ allowed: true } | { allowed: false; retryAfterSeconds: number }> {
  const rows = await loadThrottleRows(buildKey(route, ip, email));
  const now = Date.now();
  const blocked = rows.find((row) => row.blocked_until && new Date(row.blocked_until).getTime() > now);

  if (!blocked) {
    return { allowed: true };
  }

  const retryAfterSeconds = Math.max(1, Math.ceil((new Date(blocked.blocked_until!).getTime() - now) / 1000));
  return { allowed: false, retryAfterSeconds };
}

export async function recordPublicAuthHit(input: {
  route: PublicAuthRoute;
  ip: string;
  email?: string | null;
  weight?: number;
  reset?: boolean;
  suspicious?: boolean;
}): Promise<void> {
  const keys = buildKey(input.route, input.ip, input.email);
  const rows = await loadThrottleRows(keys);
  const normalizedEmail = normalizeEmail(input.email);
  const weight = input.reset ? 0 : Math.max(1, input.weight ?? (input.suspicious ? 3 : 1));
  const now = new Date();
  const newWindowStartedAt = now.toISOString();

  for (const key of keys) {
    const row = rows.find((entry) => entry.throttle_key === key);
    const freshWindow = row ? isFreshWindow(row) : true;
    const currentScore = input.reset ? 0 : (freshWindow ? 0 : row?.risk_score ?? 0);
    const nextScore = input.reset ? 0 : currentScore + weight;
    const blockMs = input.reset ? 0 : riskScoreToBlockMs(nextScore);
    const blockedUntil = blockMs > 0 ? new Date(now.getTime() + blockMs).toISOString() : null;

    await query(
      `INSERT INTO auth_abuse_controls (
        throttle_key, route, ip_address, email_hint, risk_score, window_started_at, blocked_until, last_seen_at
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
      ON CONFLICT (throttle_key) DO UPDATE SET
        route = EXCLUDED.route,
        ip_address = EXCLUDED.ip_address,
        email_hint = EXCLUDED.email_hint,
        risk_score = EXCLUDED.risk_score,
        window_started_at = EXCLUDED.window_started_at,
        blocked_until = EXCLUDED.blocked_until,
        last_seen_at = NOW()`,
      [key, input.route, normalizeIp(input.ip), normalizedEmail, nextScore, input.reset || freshWindow ? newWindowStartedAt : row?.window_started_at ?? newWindowStartedAt, blockedUntil]
    );

    if (input.suspicious || blockMs > 0) {
      void recordAppEvent({
        level: input.suspicious ? "warning" : "warning",
        source: input.suspicious ? "security.bot" : "security.throttle",
        message: input.suspicious
          ? "Honeypot activado en formulario publico"
          : "Formulario publico limitado por abuso repetido",
        details: {
          route: input.route,
          ip: normalizeIp(input.ip),
          email: normalizedEmail,
          throttleKey: key,
          riskScore: nextScore,
          blockedUntil,
        },
      });
    }
  }
}

export function getPublicAuthThrottleMessage(): string {
  return THROTTLE_MESSAGE;
}
