import { ACTIVITY_SEVERITY_ORDER, type ActivitySeverity } from "../activity/types";
import { ACTIVITY_SOURCE_IDS } from "../activity/registry";
import type { ObservabilityReportQuery } from "@overdeck/report-contract";

export const MAX_LIVE_REPORT_RANGE_MS = 90 * 24 * 60 * 60 * 1000;
const DEFAULT_REPORT_RANGE_MS = 24 * 60 * 60 * 1000;
const MAX_FILTER_VALUES = 25;
const MAX_FILTER_LENGTH = 120;
const MAX_SEARCH_LENGTH = 200;
const MAX_TIMEZONE_LENGTH = 100;

export type ReportQueryParseResult =
  | { ok: true; query: ObservabilityReportQuery }
  | { ok: false; error: string };

function defaultTimezone(): string {
  try {
    return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
  } catch {
    return "UTC";
  }
}

function validTimezone(value: string): boolean {
  try {
    new Intl.DateTimeFormat("en-US", { timeZone: value }).format(0);
    return true;
  } catch {
    return false;
  }
}

function parseTimestamp(value: string | null, fallback: number): number | null {
  if (value === null || value.trim() === "") return fallback;
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? parsed : null;
}

interface ReportSearchParams {
  get(name: string): string | null;
  getAll(name: string): string[];
}

function repeatedValues(params: ReportSearchParams, key: string): string[] | string {
  const values = [...new Set(params.getAll(key).map((value) => value.trim()).filter(Boolean))];
  if (values.length > MAX_FILTER_VALUES) return `${key} accepts at most ${MAX_FILTER_VALUES} values`;
  if (values.some((value) => value.length > MAX_FILTER_LENGTH)) return `${key} values must be ${MAX_FILTER_LENGTH} characters or fewer`;
  return values;
}

export function parseObservabilityReportQuery(
  params: ReportSearchParams,
  now: () => number = Date.now,
): ReportQueryParseResult {
  const nowMs = now();
  const toMs = parseTimestamp(params.get("to"), nowMs);
  const fromMs = parseTimestamp(params.get("from"), (toMs ?? nowMs) - DEFAULT_REPORT_RANGE_MS);
  if (fromMs === null || toMs === null) return { ok: false, error: "Report dates must be valid ISO timestamps." };
  if (toMs <= fromMs) return { ok: false, error: "Report end must be after report start." };
  if (toMs - fromMs > MAX_LIVE_REPORT_RANGE_MS) return { ok: false, error: "Reports are limited to 90 days." };

  const timezone = params.get("timezone")?.trim() || defaultTimezone();
  if (timezone.length > MAX_TIMEZONE_LENGTH || !validTimezone(timezone)) {
    return { ok: false, error: "Report timezone is not recognized." };
  }

  const projects = repeatedValues(params, "project");
  if (typeof projects === "string") return { ok: false, error: projects };
  const sources = repeatedValues(params, "source");
  if (typeof sources === "string") return { ok: false, error: sources };
  const unknownSources = sources.filter((source) => !ACTIVITY_SOURCE_IDS.includes(source));
  if (unknownSources.length > 0) return { ok: false, error: `Unknown activity source: ${unknownSources.join(", ")}` };

  const severityValue = params.get("severity")?.trim();
  const severity = severityValue as ActivitySeverity | undefined;
  if (severityValue && !(severityValue in ACTIVITY_SEVERITY_ORDER)) {
    return { ok: false, error: `Unknown severity: ${severityValue}` };
  }

  const q = params.get("q")?.trim();
  if (q && q.length > MAX_SEARCH_LENGTH) return { ok: false, error: `Search must be ${MAX_SEARCH_LENGTH} characters or fewer.` };

  return {
    ok: true,
    query: {
      from: new Date(fromMs).toISOString(),
      to: new Date(toMs).toISOString(),
      timezone,
      ...(projects.length > 0 ? { projects } : {}),
      ...(sources.length > 0 ? { sources } : {}),
      ...(severity ? { severity } : {}),
      ...(q ? { q } : {}),
    },
  };
}
