import { NextRequest, NextResponse } from "next/server";
import { upsertEvents } from "@/lib/chronicle-db";
import type { ChronicleEvent, EventSource } from "@/lib/chronicle-db";
import { randomUUID } from "crypto";

const CALENDAR_URL = process.env.CALENDAR_SERVICE_URL || "http://127.0.0.1:8084";
const BRIDGE_URL = process.env.IMESSAGE_BRIDGE_URL || "http://127.0.0.1:8082";
const MUSIC_URL = process.env.MUSIC_SERVICE_URL || "http://127.0.0.1:8085";
const CHANNEL_URL = process.env.CHANNEL_URL || "http://127.0.0.1:7100";
const CHANNEL_SECRET = process.env.CHANNEL_SECRET || "";

interface IngestResult {
  source: EventSource;
  count: number;
  error?: string;
}

export async function POST(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  // Optional: only ingest since a specific date (default: last 30 days)
  const since = searchParams.get("since") ?? daysAgo(30);
  const sources = (searchParams.get("sources") ?? "calendar,imessage,agent,music").split(",") as EventSource[];

  const results: IngestResult[] = [];
  const allEvents: ChronicleEvent[] = [];

  await Promise.allSettled(
    sources.map(async (source) => {
      try {
        let events: ChronicleEvent[] = [];
        switch (source) {
          case "calendar":
            events = await ingestCalendar(since);
            break;
          case "imessage":
            events = await ingestImessage(since);
            break;
          case "agent":
            events = await ingestAgents(since);
            break;
          case "music":
            events = await ingestMusic(since);
            break;
        }
        allEvents.push(...events);
        results.push({ source, count: events.length });
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        console.error(`[chronicle/ingest] ${source} failed:`, msg);
        results.push({ source, count: 0, error: msg });
      }
    })
  );

  if (allEvents.length > 0) {
    upsertEvents(allEvents);
  }

  const total = results.reduce((s, r) => s + r.count, 0);
  return NextResponse.json({ ingested: total, results, since });
}

// ─── Calendar ingestion ───────────────────────────────────────────────────────

interface GoogleEvent {
  id: string;
  summary?: string;
  description?: string;
  start?: { dateTime?: string; date?: string };
  end?: { dateTime?: string; date?: string };
  status?: string;
  attendees?: { email: string; displayName?: string }[];
}

async function ingestCalendar(since: string): Promise<ChronicleEvent[]> {
  const res = await fetch(
    `${CALENDAR_URL}/google/cached-events?since=${encodeURIComponent(since)}`,
    { signal: AbortSignal.timeout(10_000) }
  );
  if (!res.ok) throw new Error(`Calendar service ${res.status}`);

  const data = await res.json() as { events?: GoogleEvent[] };
  const events = data.events ?? [];

  return events
    .filter((e) => e.status !== "cancelled")
    .map((e): ChronicleEvent => {
      const startStr = e.start?.dateTime ?? e.start?.date ?? "";
      const ts = startStr ? new Date(startStr).getTime() : Date.now();
      const date = new Date(ts).toISOString().slice(0, 10);
      const attendeeNames = (e.attendees ?? [])
        .map((a) => a.displayName ?? a.email)
        .slice(0, 5)
        .join(", ");

      return {
        id: `cal-${e.id}`,
        date,
        timestamp: ts,
        source: "calendar",
        type: "meeting",
        title: e.summary ?? "Untitled event",
        body: e.description?.slice(0, 200) || undefined,
        metadata: attendeeNames ? { attendees: attendeeNames } : undefined,
      };
    });
}

// ─── iMessage ingestion ───────────────────────────────────────────────────────

interface IMessage {
  id?: string;
  guid?: string;
  timestamp?: number;
  date?: string;
  text?: string;
  handle?: string;
  contact?: string;
  is_from_me?: boolean;
}

async function ingestImessage(since: string): Promise<ChronicleEvent[]> {
  const sinceTs = new Date(since).getTime();
  const res = await fetch(`${BRIDGE_URL}/history?limit=500`, {
    signal: AbortSignal.timeout(10_000),
  });
  if (!res.ok) throw new Error(`iMessage bridge ${res.status}`);

  const data = await res.json() as { messages?: IMessage[] };
  const messages = data.messages ?? [];

  const seen = new Map<string, number>(); // contact → count

  return messages
    .filter((m) => {
      const ts = m.timestamp ?? (m.date ? new Date(m.date).getTime() : 0);
      return ts >= sinceTs;
    })
    .map((m): ChronicleEvent => {
      const ts = m.timestamp ?? (m.date ? new Date(m.date).getTime() : Date.now());
      const date = new Date(ts).toISOString().slice(0, 10);
      const contact = m.handle ?? m.contact ?? "Unknown";
      const key = `${date}-${contact}`;
      seen.set(key, (seen.get(key) ?? 0) + 1);

      const direction = m.is_from_me ? "to" : "from";

      return {
        id: `msg-${m.id ?? m.guid ?? randomUUID()}`,
        date,
        timestamp: ts,
        source: "imessage",
        type: "message",
        title: `iMessage ${direction} ${contact}`,
        body: m.text?.slice(0, 200) || undefined,
        metadata: { contact, isFromMe: m.is_from_me },
      };
    });
}

// ─── Agent conversation ingestion ─────────────────────────────────────────────

interface AgentConversation {
  id: string;
  messages: { role: string; text: string; timestamp: number }[];
  created_at: number;
  updated_at: number;
}

async function ingestAgents(since: string): Promise<ChronicleEvent[]> {
  const sinceTs = new Date(since).getTime();
  const events: ChronicleEvent[] = [];

  try {
    const listRes = await fetch(`${CHANNEL_URL}/conversations`, {
      headers: { Authorization: `Bearer ${CHANNEL_SECRET}` },
      signal: AbortSignal.timeout(10_000),
    });
    if (!listRes.ok) throw new Error(`Channel ${listRes.status}`);

    const { conversations } = await listRes.json() as { conversations: AgentConversation[] };

    for (const conv of conversations) {
      if (conv.created_at < sinceTs) continue;

      const userMessages = conv.messages.filter((m) => m.role === "user");
      const firstUser = userMessages[0];
      if (!firstUser) continue;

      const date = new Date(conv.created_at).toISOString().slice(0, 10);
      const preview = firstUser.text.slice(0, 120).replace(/\n/g, " ");

      events.push({
        id: `agent-conv-${conv.id}`,
        date,
        timestamp: conv.created_at,
        source: "agent",
        type: "conversation",
        title: `Agent task: ${preview}${firstUser.text.length > 120 ? "…" : ""}`,
        body: undefined,
        metadata: {
          conversationId: conv.id,
          messageCount: conv.messages.length,
          agentName: process.env.PRIMARY_AGENT ?? "",
        },
      });
    }
  } catch (err) {
    // Agent channel may not be running; that's fine
    console.warn("[chronicle/ingest] Agent channel unavailable:", err instanceof Error ? err.message : err);
  }

  return events;
}

// ─── Music ingestion ──────────────────────────────────────────────────────────

interface CaptureEntry {
  id: string | number;
  title?: string;
  artist?: string;
  album?: string;
  captured_at?: number;
  completed_at?: number;
  status?: string;
}

async function ingestMusic(since: string): Promise<ChronicleEvent[]> {
  const sinceTs = new Date(since).getTime();

  // Try the music service queue (captured tracks)
  const res = await fetch(`${MUSIC_URL}/queue?limit=500`, {
    signal: AbortSignal.timeout(10_000),
  });
  if (!res.ok) throw new Error(`Music service ${res.status}`);

  const data = await res.json() as { queue?: CaptureEntry[] };
  const queue = data.queue ?? [];

  return queue
    .filter((item) => {
      const ts = item.completed_at ?? item.captured_at ?? 0;
      return ts >= sinceTs && item.status === "completed";
    })
    .map((item): ChronicleEvent => {
      const ts = item.completed_at ?? item.captured_at ?? Date.now();
      const date = new Date(ts).toISOString().slice(0, 10);

      return {
        id: `music-${item.id}`,
        date,
        timestamp: ts,
        source: "music",
        type: "track",
        title: [item.title, item.artist].filter(Boolean).join(" — ") || "Unknown track",
        body: item.album || undefined,
        metadata: { artist: item.artist, album: item.album, title: item.title },
      };
    });
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function daysAgo(n: number): string {
  const d = new Date();
  d.setDate(d.getDate() - n);
  return d.toISOString().slice(0, 10);
}
