import type { DatabaseAdapter } from "../db.js";
import { ValidationError, assertAllowedKeys } from "../errors.js";
import { ALL_SOURCES, type AnalyticsSource } from "../sources.js";

const GROUP_BY_VALUES = ["source", "project", "model", "day", "event_type", "event_name"] as const;

interface SummaryInput {
  sources?: AnalyticsSource[];
  date_from?: string;
  date_to?: string;
  group_by?: string[];
}

function parseIso(value: unknown): string | undefined {
  if (value === undefined) return undefined;
  if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
    throw new ValidationError("date_from/date_to must be valid ISO strings.");
  }
  return new Date(value).toISOString();
}

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

  const sources = obj.sources as unknown;
  const groupByRaw = obj.group_by as unknown;
  let parsedSources: AnalyticsSource[] | undefined;

  if (Array.isArray(sources)) {
    parsedSources = [];
    for (const src of sources) {
      if (typeof src !== "string" || !ALL_SOURCES.includes(src as AnalyticsSource)) {
        throw new ValidationError("sources contains unsupported values.");
      }
      if (!parsedSources.includes(src as AnalyticsSource)) parsedSources.push(src as AnalyticsSource);
    }
  }

  let group_by: string[] | undefined;
  if (Array.isArray(groupByRaw)) {
    group_by = [];
    for (const g of groupByRaw) {
      if (typeof g !== "string" || !GROUP_BY_VALUES.includes(g as (typeof GROUP_BY_VALUES)[number])) {
        throw new ValidationError("group_by contains unsupported values.");
      }
      if (!group_by.includes(g)) group_by.push(g);
    }
  }

  return {
    sources: parsedSources,
    date_from: parseIso(obj.date_from),
    date_to: parseIso(obj.date_to),
    group_by
  };
}

export async function querySummary(db: DatabaseAdapter, args: unknown): Promise<Record<string, unknown>> {
  const input = parseInput(args);
  const sources = input.sources ?? [...ALL_SOURCES];
  const sessionSources = sources.filter((s) => s !== "hook_log");
  const eventSources = sources;

  const sessionWhere: string[] = [];
  const sessionParams: unknown[] = [];
  if (sessionSources.length > 0) {
    sessionWhere.push(`source IN (${sessionSources.map(() => "?").join(",")})`);
    sessionParams.push(...sessionSources);
  } else {
    sessionWhere.push("1=0");
  }
  if (input.date_from) {
    sessionWhere.push("updated_at >= ?");
    sessionParams.push(input.date_from);
  }
  if (input.date_to) {
    sessionWhere.push("updated_at <= ?");
    sessionParams.push(input.date_to);
  }
  const sessionTotals = await db.get<{ sessions: number; messages: number }>(
    `SELECT COUNT(*) as sessions, COALESCE(SUM(message_count),0) as messages FROM sessions WHERE ${sessionWhere.join(" AND ")}`,
    sessionParams
  );

  const eventWhere: string[] = [];
  const eventParams: unknown[] = [];
  if (eventSources.length > 0) {
    eventWhere.push(`source IN (${eventSources.map(() => "?").join(",")})`);
    eventParams.push(...eventSources);
  }
  if (input.date_from) {
    eventWhere.push("occurred_at >= ?");
    eventParams.push(input.date_from);
  }
  if (input.date_to) {
    eventWhere.push("occurred_at <= ?");
    eventParams.push(input.date_to);
  }
  eventWhere.push("is_self_event = 0");

  const eventTotals = await db.get<{ runtime_events: number; mcp_calls: number; hook_events: number; skill_hints: number; agent_tools: number; agent_lifecycle: number }>(
    `SELECT
       COUNT(*) as runtime_events,
       SUM(CASE WHEN event_type='mcp' THEN 1 ELSE 0 END) as mcp_calls,
       SUM(CASE WHEN event_type='hook' THEN 1 ELSE 0 END) as hook_events,
       SUM(CASE WHEN event_type='skill' THEN 1 ELSE 0 END) as skill_hints,
       SUM(CASE WHEN event_type='agent_tool' THEN 1 ELSE 0 END) as agent_tools,
       SUM(CASE WHEN event_type='agent_lifecycle' THEN 1 ELSE 0 END) as agent_lifecycle
     FROM runtime_events WHERE ${eventWhere.join(" AND ")}`,
    eventParams
  );

  const groupByRows: Array<Record<string, unknown>> = [];
  if (input.group_by && input.group_by.length > 0) {
    const allowedExpr: Record<string, string> = {
      source: "source",
      event_type: "event_type",
      event_name: "event_name",
      day: "substr(occurred_at,1,10)",
      project: "COALESCE((SELECT project_id FROM sessions s WHERE s.id = runtime_events.session_id),'')",
      model: "COALESCE((SELECT model_primary FROM sessions s WHERE s.id = runtime_events.session_id),'')"
    };
    const selects = input.group_by.map((g) => `${allowedExpr[g]} as ${g}`);
    const groupBySql = input.group_by.join(", ");
    const grouped = await db.all<Record<string, unknown>>(
      `SELECT ${selects.join(", ")}, COUNT(*) as count
       FROM runtime_events
       WHERE ${eventWhere.join(" AND ")}
       GROUP BY ${groupBySql}
       ORDER BY count DESC`,
      eventParams
    );
    groupByRows.push(...grouped);
  }

  return {
    ok: true,
    session_totals: { sessions: sessionTotals?.sessions ?? 0, messages: sessionTotals?.messages ?? 0 },
    event_totals: {
      runtime_events: eventTotals?.runtime_events ?? 0,
      mcp_calls: eventTotals?.mcp_calls ?? 0,
      hook_events: eventTotals?.hook_events ?? 0,
      skill_hints: eventTotals?.skill_hints ?? 0,
      agent_tools: eventTotals?.agent_tools ?? 0,
      agent_lifecycle: eventTotals?.agent_lifecycle ?? 0
    },
    notes: ["hook_log is included only in event_totals", "self events are excluded by default"],
    ...(input.group_by ? { group_by: input.group_by, groups: groupByRows } : {})
  };
}
