import type { DatabaseAdapter } from "../db.js";
import { ValidationError, assertAllowedKeys } from "../errors.js";
import { SESSION_SOURCES, type AnalyticsSource } from "../sources.js";
import { parseOptionalEnumArray } from "../validation.js";
import { normalizeCodexThreadName } from "../codex-session-index.js";

interface SessionsInput {
  sources?: AnalyticsSource[];
  date_from?: string;
  date_to?: string;
  limit: number;
  offset: number;
  include_technical?: boolean;
}

function parseIso(value: unknown): string | undefined {
  if (value === undefined) return undefined;
  if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new ValidationError("Invalid date.");
  return new Date(value).toISOString();
}

function parseInput(args: unknown): SessionsInput {
  const obj = (args && typeof args === "object" && !Array.isArray(args) ? args : {}) as Record<string, unknown>;
  assertAllowedKeys(obj, ["sources", "date_from", "date_to", "limit", "offset", "include_technical"]);

  const sources = parseOptionalEnumArray(obj.sources, SESSION_SOURCES, "sources");
  if (obj.limit !== undefined && typeof obj.limit !== "number") throw new ValidationError("limit must be a number.");
  if (obj.offset !== undefined && typeof obj.offset !== "number") throw new ValidationError("offset must be a number.");
  if (obj.include_technical !== undefined && typeof obj.include_technical !== "boolean") throw new ValidationError("include_technical must be a boolean.");
  const limit = typeof obj.limit === "number" ? Math.max(1, Math.min(1000, Math.floor(obj.limit))) : 100;
  const offset = typeof obj.offset === "number" ? Math.max(0, Math.floor(obj.offset)) : 0;
  return { sources, date_from: parseIso(obj.date_from), date_to: parseIso(obj.date_to), limit, offset, include_technical: obj.include_technical };
}

export async function querySessions(db: DatabaseAdapter, args: unknown): Promise<Record<string, unknown>> {
  const input = parseInput(args);
  const where: string[] = [`source IN (${SESSION_SOURCES.map(() => "?").join(",")})`];
  const params: unknown[] = [...SESSION_SOURCES];
  if (input.sources) {
    where.push(`source IN (${input.sources.map(() => "?").join(",")})`);
    params.push(...input.sources);
  }
  if (input.date_from) {
    where.push("updated_at >= ?");
    params.push(input.date_from);
  }
  if (input.date_to) {
    where.push("updated_at <= ?");
    params.push(input.date_to);
  }
  if (input.include_technical === false) {
    where.push("COALESCE(session_kind, 'main') = 'main'");
    where.push("user_message_count > 0");
  }

  const total = await db.get<{ total: number }>(`SELECT COUNT(*) as total FROM sessions WHERE ${where.join(" AND ")}`, params);
  const rows = await db.all<{
    id: string;
    source: string;
    project_id: string | null;
    mode: string;
    model_primary: string | null;
    token_available: number;
    input_tokens: number;
    output_tokens: number;
    message_count: number;
    user_message_count: number;
    assistant_message_count: number;
    session_kind: string | null;
    created_at: string | null;
    updated_at: string;
    metadata_json: string | null;
  }>(
    `
    SELECT s.id, s.source, s.project_id, s.mode, s.model_primary, s.token_available, s.message_count, s.user_message_count, s.assistant_message_count, s.session_kind, s.created_at, s.updated_at, s.metadata_json,
      COALESCE((SELECT SUM(COALESCE(mm.input_tokens, 0)) FROM message_metrics mm WHERE mm.session_id = s.id), 0) AS input_tokens,
      COALESCE((SELECT SUM(COALESCE(mm.output_tokens, 0)) FROM message_metrics mm WHERE mm.session_id = s.id), 0) AS output_tokens
    FROM sessions s
    WHERE ${where.join(" AND ")}
    ORDER BY updated_at DESC, id ASC
    LIMIT ? OFFSET ?
    `,
    [...params, input.limit, input.offset]
  );

  const totalValue = total?.total ?? 0;
  return {
    ok: true,
    total: totalValue,
    limit: input.limit,
    offset: input.offset,
    has_more: input.offset + rows.length < totalValue,
    items: rows.map(({ metadata_json, ...row }) => {
      let threadName: string | null = null;
      if (row.source === "codex" && metadata_json) {
        try {
          const metadata: unknown = JSON.parse(metadata_json);
          if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
            threadName = normalizeCodexThreadName((metadata as Record<string, unknown>).thread_name);
          }
        } catch {
          threadName = null;
        }
      }
      return { ...row, thread_name: threadName };
    })
  };
}
