import { db } from "@/db";
import { clients, projects, phases, tasks, deliverables, comments } from "@/db/schema";
import { eq, inArray, asc } from "drizzle-orm";
import type { Comment } from "@/db/schema";

// ── Types ─────────────────────────────────────────────────────────────────────

export type ConversationSummary = {
  clientId: string;
  name: string;
  brand_name: string;
  token: string;
  slug: string | null;
  lastMessage: string;
  lastMessageAuthor: "client" | "admin";
  lastMessageAt: Date;
  lastEntityLabel: string;
  unread: boolean;
  unreadCount: number;
};

export type ThreadMessage = {
  id: string;
  author: "client" | "admin";
  body: string;
  created_at: Date;
  entityLabel: string;
  entityType: string;
};

export type ConversationThread = {
  clientId: string;
  name: string;
  brand_name: string;
  token: string;
  slug: string | null;
  messages: ThreadMessage[];
};

type ClientMeta = {
  id: string;
  name: string;
  brand_name: string;
  token: string;
  slug: string | null;
  admin_last_read_at: Date | null;
};

type EntityInfo = { clientId: string; label: string; type: string };

// ── Shared aggregation ──────────────────────────────────────────────────────────
// Builds the entity_id → owning client map by walking
// clients → projects → phases → tasks → deliverables, then groups every comment
// under its owning client. Same pattern as admin-queries.getClientFullDetail,
// but across ALL non-archived clients at once (cross-client inbox).

async function buildEntityMap(): Promise<{
  clientMeta: Map<string, ClientMeta>;
  entityMap: Map<string, EntityInfo>;
  allEntityIds: string[];
}> {
  const clientMeta = new Map<string, ClientMeta>();
  const entityMap = new Map<string, EntityInfo>();

  const clientRows = await db
    .select()
    .from(clients)
    .where(eq(clients.archived, false));

  for (const c of clientRows) {
    clientMeta.set(c.id, {
      id: c.id,
      name: c.name,
      brand_name: c.brand_name,
      token: c.token,
      slug: c.slug,
      admin_last_read_at: c.admin_last_read_at,
    });
    // A client's own id is the entity_id for "general" messages.
    entityMap.set(c.id, { clientId: c.id, label: "Generale", type: "general" });
  }

  const clientIds = clientRows.map((c) => c.id);
  if (clientIds.length === 0) {
    return { clientMeta, entityMap, allEntityIds: [] };
  }

  const projectRows = await db
    .select({ id: projects.id, client_id: projects.client_id })
    .from(projects)
    .where(inArray(projects.client_id, clientIds));
  const projectToClient = new Map(projectRows.map((p) => [p.id, p.client_id]));
  const projectIds = projectRows.map((p) => p.id);

  if (projectIds.length > 0) {
    const phaseRows = await db
      .select({ id: phases.id, project_id: phases.project_id, title: phases.title })
      .from(phases)
      .where(inArray(phases.project_id, projectIds));
    const phaseToClient = new Map<string, string>();
    for (const ph of phaseRows) {
      const clientId = projectToClient.get(ph.project_id);
      if (!clientId) continue;
      phaseToClient.set(ph.id, clientId);
      entityMap.set(ph.id, { clientId, label: `Fase: ${ph.title}`, type: "phase" });
    }

    const phaseIds = phaseRows.map((p) => p.id);
    if (phaseIds.length > 0) {
      const taskRows = await db
        .select({ id: tasks.id, phase_id: tasks.phase_id, title: tasks.title })
        .from(tasks)
        .where(inArray(tasks.phase_id, phaseIds));
      const taskToClient = new Map<string, string>();
      for (const t of taskRows) {
        const clientId = phaseToClient.get(t.phase_id);
        if (!clientId) continue;
        taskToClient.set(t.id, clientId);
        entityMap.set(t.id, { clientId, label: `Task: ${t.title}`, type: "task" });
      }

      const taskIds = taskRows.map((t) => t.id);
      if (taskIds.length > 0) {
        const delivRows = await db
          .select({ id: deliverables.id, task_id: deliverables.task_id, title: deliverables.title })
          .from(deliverables)
          .where(inArray(deliverables.task_id, taskIds));
        for (const d of delivRows) {
          const clientId = taskToClient.get(d.task_id);
          if (!clientId) continue;
          entityMap.set(d.id, {
            clientId,
            label: `Deliverable: ${d.title}`,
            type: "deliverable",
          });
        }
      }
    }
  }

  return { clientMeta, entityMap, allEntityIds: [...entityMap.keys()] };
}

// Groups comments (already sorted asc) by owning client id.
function groupCommentsByClient(
  commentRows: Comment[],
  entityMap: Map<string, EntityInfo>
): Map<string, Comment[]> {
  const byClient = new Map<string, Comment[]>();
  for (const c of commentRows) {
    const info = entityMap.get(c.entity_id);
    if (!info) continue; // orphaned comment (deleted entity) — skip
    const arr = byClient.get(info.clientId) ?? [];
    arr.push(c);
    byClient.set(info.clientId, arr);
  }
  return byClient;
}

async function fetchComments(allEntityIds: string[]): Promise<Comment[]> {
  if (allEntityIds.length === 0) return [];
  return db
    .select()
    .from(comments)
    .where(inArray(comments.entity_id, allEntityIds))
    .orderBy(asc(comments.created_at));
}

// ── Public API ──────────────────────────────────────────────────────────────────

/** One entry per non-archived client that has at least one comment, newest first. */
export async function getConversations(): Promise<ConversationSummary[]> {
  const { clientMeta, entityMap, allEntityIds } = await buildEntityMap();
  const commentRows = await fetchComments(allEntityIds);
  const byClient = groupCommentsByClient(commentRows, entityMap);

  const summaries: ConversationSummary[] = [];
  for (const [clientId, msgs] of byClient) {
    const meta = clientMeta.get(clientId);
    if (!meta || msgs.length === 0) continue;

    const last = msgs[msgs.length - 1];
    const lastReadAt = meta.admin_last_read_at?.getTime() ?? 0;
    const clientMsgsAfterRead = msgs.filter(
      (m) => m.author === "client" && m.created_at.getTime() > lastReadAt
    );

    summaries.push({
      clientId,
      name: meta.name,
      brand_name: meta.brand_name,
      token: meta.token,
      slug: meta.slug,
      lastMessage: last.body,
      lastMessageAuthor: last.author as "client" | "admin",
      lastMessageAt: last.created_at,
      lastEntityLabel: entityMap.get(last.entity_id)?.label ?? "Generale",
      unread: clientMsgsAfterRead.length > 0,
      unreadCount: clientMsgsAfterRead.length,
    });
  }

  summaries.sort((a, b) => b.lastMessageAt.getTime() - a.lastMessageAt.getTime());
  return summaries;
}

/** Full ordered thread for a single client, with per-message entity labels. */
export async function getConversationThread(
  clientId: string
): Promise<ConversationThread | null> {
  const { clientMeta, entityMap, allEntityIds } = await buildEntityMap();
  const meta = clientMeta.get(clientId);
  if (!meta) return null;

  // Restrict to entity ids owned by this client.
  const ownEntityIds = allEntityIds.filter(
    (id) => entityMap.get(id)?.clientId === clientId
  );
  const commentRows = await fetchComments(ownEntityIds);

  const messages: ThreadMessage[] = commentRows.map((c) => {
    const info = entityMap.get(c.entity_id);
    return {
      id: c.id,
      author: c.author as "client" | "admin",
      body: c.body,
      created_at: c.created_at,
      entityLabel: info?.label ?? "Generale",
      entityType: info?.type ?? "general",
    };
  });

  return {
    clientId: meta.id,
    name: meta.name,
    brand_name: meta.brand_name,
    token: meta.token,
    slug: meta.slug,
    messages,
  };
}

/** Number of clients whose conversation has unread client messages (sidebar badge). */
export async function getUnreadConversationsCount(): Promise<number> {
  const { clientMeta, entityMap, allEntityIds } = await buildEntityMap();
  const commentRows = await fetchComments(allEntityIds);
  const byClient = groupCommentsByClient(commentRows, entityMap);

  let count = 0;
  for (const [clientId, msgs] of byClient) {
    const meta = clientMeta.get(clientId);
    if (!meta) continue;
    const lastReadAt = meta.admin_last_read_at?.getTime() ?? 0;
    const hasUnread = msgs.some(
      (m) => m.author === "client" && m.created_at.getTime() > lastReadAt
    );
    if (hasUnread) count++;
  }
  return count;
}
