import type { DatabaseAdapter } from "../db.js";
import { hmacHex } from "../privacy.js";
import { isAllowedMcpServer } from "../repo-catalog.js";

interface ImportContext {
  db: DatabaseAdapter;
  sourceFileId: string;
  machineId: string;
  hmacSalt: string;
}

interface ImportCounters {
  sessions_upserted: number;
  messages_upserted: number;
  runtime_events_upserted: number;
}

interface WarningEntry {
  code: string;
  message_code: string;
  severity: "warning" | "error";
  source_file_id?: string;
  details?: Record<string, number | string | boolean>;
}

export interface HookImportLine {
  lineNo: number;
  rawLine: string;
}

function sanitizeWarning(input: WarningEntry): WarningEntry {
  return {
    code: input.code,
    message_code: input.code,
    severity: input.severity,
    ...(input.source_file_id ? { source_file_id: input.source_file_id } : {}),
    ...(input.details ? { details: input.details } : {})
  };
}

function toIsoOrNow(value: unknown): string {
  if (typeof value === "string" && Number.isFinite(Date.parse(value))) return new Date(value).toISOString();
  return new Date().toISOString();
}

function safeName(value: unknown): string | null {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  if (!trimmed || trimmed.length > 180) return null;
  return /^[A-Za-z0-9_.:-]+$/u.test(trimmed) ? trimmed : null;
}

export async function importHookLogLines(
  context: ImportContext,
  entries: readonly HookImportLine[]
): Promise<{ counters: ImportCounters; warnings: WarningEntry[] }> {
  const counters: ImportCounters = { sessions_upserted: 0, messages_upserted: 0, runtime_events_upserted: 0 };
  const warnings: WarningEntry[] = [];

  await context.db.exec("BEGIN IMMEDIATE TRANSACTION");
  try {
    for (const entry of entries) {
      const line = entry.rawLine.trim();
      if (!line) continue;
      let parsed: Record<string, unknown>;
      try {
        parsed = JSON.parse(line) as Record<string, unknown>;
      } catch {
        warnings.push(
          sanitizeWarning({
            code: "INVALID_JSON_LINE_SKIPPED",
            message_code: "INVALID_JSON_LINE_SKIPPED",
            severity: "warning",
            source_file_id: context.sourceFileId,
            details: { line_no: entry.lineNo }
          })
        );
        continue;
      }

      const event = typeof parsed.event === "string" ? parsed.event : "Unknown";
      if (event === "SkillUsed") {
        continue;
      }
      const occurredAt = toIsoOrNow(parsed.ts);

      let eventType: "mcp" | "skill" | "hook" | "agent_tool" | "agent_lifecycle" = "hook";
      let eventOrigin: "observed_structured" | "hook_hint" = "hook_hint";
      let eventName = typeof parsed.hook === "string" ? parsed.hook : event;
      let mcpServerName: string | null = null;
      let toolName: string | null = null;
      let skillName: string | null = null;
      const hookName: string | null = typeof parsed.hook === "string" ? parsed.hook : null;
      const hookEvent: string | null = event;
      let metadata: Record<string, unknown> | null = null;
      let isSelfEvent = 0;

      if (event === "McpHint") {
        eventType = "hook";
        eventOrigin = "hook_hint";
        const rawServer = typeof parsed.mcp_server === "string" ? parsed.mcp_server : "";
        const rawToolName = safeName(parsed.tool_name);
        if (isAllowedMcpServer(rawServer)) {
          mcpServerName = rawServer;
          toolName = rawToolName;
          eventName = toolName ? `${mcpServerName}.${toolName}` : `mcp_hint.${mcpServerName}`;
          metadata = {
            kind: "mcp_hint",
            mcp_server_name: rawServer,
            ...(toolName ? { tool_name: toolName } : {}),
            ...(safeName(parsed.raw_tool_name) ? { raw_tool_name: safeName(parsed.raw_tool_name) } : {}),
            timestamp_source: "native"
          };
          if (rawServer === "analytics-mcp-server") isSelfEvent = 1;
        } else {
          metadata = { kind: "mcp_hint", timestamp_source: "native" };
        }
      } else if (event === "SkillHint") {
        eventType = "skill";
        eventOrigin = "hook_hint";
        skillName = typeof parsed.skill === "string" ? parsed.skill : null;
        eventName = skillName ? `skill.${skillName}` : "skill.unknown";
        metadata = { kind: "skill_hint", timestamp_source: "native" };
      } else if (event === "HookCall" || event === "SessionStart" || event === "UserPromptSubmit") {
        eventType = "hook";
        eventOrigin = "hook_hint";
        metadata = { kind: "hook_call", timestamp_source: "native" };
      }

      const argsObj =
        parsed.args && typeof parsed.args === "object" && !Array.isArray(parsed.args)
          ? (parsed.args as Record<string, unknown>)
          : {};
      const argsKeys = Object.keys(argsObj).filter((k) => /^[a-zA-Z0-9_]{1,80}$/u.test(k)).sort();
      const argsKeysJson = JSON.stringify(argsKeys);
      const argsStr = JSON.stringify(argsObj);
      const argsHash = hmacHex(argsStr, context.hmacSalt);
      const argsBytes = Buffer.byteLength(argsStr, "utf8");
      const eventId = hmacHex(
        `${context.machineId}:hook_log:${context.sourceFileId}:${entry.lineNo}:${eventType}:${eventName}:${occurredAt}`,
        context.hmacSalt
      );

      await context.db.run(
        `
          INSERT OR IGNORE INTO runtime_events(
            id, source, source_file_id, session_id, event_type, event_origin, event_name, mcp_server_name, tool_name,
            skill_name, hook_name, hook_event, source_line_no, event_seq, args_keys_json, args_hash, args_bytes,
            occurred_at, timestamp_source, is_self_event, metadata_json
          ) VALUES (?, 'hook_log', ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'native', ?, ?)
        `,
        [
          eventId,
          context.sourceFileId,
          eventType,
          eventOrigin,
          eventName,
          mcpServerName,
          toolName,
          skillName,
          hookName,
          hookEvent,
          entry.lineNo,
          entry.lineNo,
          argsKeysJson,
          argsHash,
          argsBytes,
          occurredAt,
          isSelfEvent,
          metadata ? JSON.stringify(metadata) : null
        ]
      );
      const observationKind =
        event === "SkillHint" ? "skill_hint" : event === "McpHint" ? "hook_hint" : "hook_log";
      const observationId = hmacHex(`${eventId}:hook_log:${context.sourceFileId}:${entry.lineNo}:${observationKind}`, context.hmacSalt);
      await context.db.run(
        `INSERT OR IGNORE INTO runtime_event_observations(
          id, runtime_event_id, observed_source, source_file_id, session_id, source_line_no, event_seq, observation_kind, observed_at
        ) VALUES (?, ?, 'hook_log', ?, NULL, ?, ?, ?, ?)`,
        [observationId, eventId, context.sourceFileId, entry.lineNo, entry.lineNo, observationKind, occurredAt]
      );
      counters.runtime_events_upserted += 1;
    }
    await context.db.exec("COMMIT");
  } catch (error) {
    await context.db.exec("ROLLBACK");
    throw error;
  }

  return { counters, warnings };
}
