import type { FileUIPart } from "ai";
import type { SupabaseClient } from "@supabase/supabase-js";
import type { ChatAttachment, ChatMessage, ChatSession } from "@/lib/chat-store";
import { chatTitleFromMessages, createId } from "@/lib/chat-store";
import type { GlmUser } from "@/lib/supabase";

export const WMC_APP_KEY = "cotizador_wmc";
export const WMC_UPLOAD_BUCKET = "cotizador-wmc-uploads";

const DEFAULT_CHAT_WEBHOOK_URL =
  "https://agenteit.digitalcompass.agency/webhook/cotizador-wmc-chat";

const SESSION_STORAGE_KEY = "cotizador_wmc_app_session_id";
const OPEN_TRACKED_KEY = "cotizador_wmc_app_open_tracked";

export type AgentResponse = {
  ok?: boolean;
  reply?: string;
  action?: string;
  requiresConfirmation?: boolean;
  pendingActionId?: string | null;
  warnings?: string[];
  confidence?: number | null;
  model?: string;
  artifactUrl?: string | null;
  artifactType?: string | null;
  quoteId?: string | null;
  artifact?: unknown;
  quote?: unknown;
  [key: string]: unknown;
};

export type TariffItem = {
  id: string;
  country_code: string;
  country_name: string;
  item_code: string;
  category: string;
  name: string;
  detail: string | null;
  mandatory_requirement: string | null;
  format_scope: string;
  period: string | null;
  unit_type: string | null;
  base_cost_usd: number;
  source_name: string;
  effective_from: string;
  effective_to: string | null;
  is_active: boolean;
};

export type TariffDraft = {
  countryCode: string;
  country: string;
  itemCode: string;
  category: string;
  name: string;
  detail?: string;
  mandatoryRequirement?: string;
  formatScope?: string;
  period?: string;
  unitType?: string;
  baseCostUsd: number;
  sourceName?: string;
  effectiveFrom?: string;
};

function getChatWebhookUrl() {
  const value = import.meta.env.VITE_CHAT_WEBHOOK_URL;
  return typeof value === "string" && value.trim() ? value.trim() : DEFAULT_CHAT_WEBHOOK_URL;
}

function safeFileName(value: string) {
  const cleaned = value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-zA-Z0-9._-]+/g, "-")
    .replace(/-+/g, "-")
    .replace(/^-|-$/g, "");
  return cleaned.slice(0, 120) || "archivo";
}

async function getAccessToken(supabase: SupabaseClient) {
  const {
    data: { session },
    error,
  } = await supabase.auth.getSession();
  if (error) throw error;
  if (session?.access_token) return session.access_token;

  const { data, error: refreshError } = await supabase.auth.refreshSession();
  if (refreshError || !data.session?.access_token) {
    throw new Error("Tu sesión expiró. Vuelve a iniciar sesión.");
  }
  return data.session.access_token;
}

export function getAppSessionId() {
  if (typeof window === "undefined") return `server-${Date.now()}`;
  let value = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
  if (!value) {
    value = `wmc-${Date.now()}-${createId()}`;
    window.sessionStorage.setItem(SESSION_STORAGE_KEY, value);
  }
  return value;
}

export function resetAppSessionTracking() {
  if (typeof window === "undefined") return;
  window.sessionStorage.removeItem(SESSION_STORAGE_KEY);
  window.sessionStorage.removeItem(OPEN_TRACKED_KEY);
}

export async function trackAppEvent(
  supabase: SupabaseClient,
  user: GlmUser,
  eventName: string,
  metadata: Record<string, unknown> = {},
  entityType?: string | null,
  entityId?: string | null,
) {
  try {
    const { error } = await supabase.rpc("glm_track_app_event", {
      p_app_key: WMC_APP_KEY,
      p_event_name: eventName,
      p_session_id: getAppSessionId(),
      p_metadata: metadata,
      p_entity_type: entityType ?? null,
      p_entity_id: entityId ?? null,
      p_display_name: user.name,
    });
    if (error) throw error;
    return true;
  } catch (error) {
    // La analítica nunca debe bloquear el uso de la app.
    console.warn("No se pudo registrar analítica del Cotizador WMC:", error);
    return false;
  }
}

export async function trackAppOpenOnce(supabase: SupabaseClient, user: GlmUser) {
  if (typeof window === "undefined") return;
  if (window.sessionStorage.getItem(OPEN_TRACKED_KEY) === user.id) return;
  const tracked = await trackAppEvent(supabase, user, "app_open", {
    path: window.location.pathname,
    userAgent: navigator.userAgent,
  });
  if (tracked) window.sessionStorage.setItem(OPEN_TRACKED_KEY, user.id);
}

export async function loadTariffItems(supabase: SupabaseClient): Promise<TariffItem[]> {
  const { data, error } = await supabase
    .from("cotizador_wmc_tariff_items")
    .select(
      "id,country_code,country_name,item_code,category,name,detail,mandatory_requirement,format_scope,period,unit_type,base_cost_usd,source_name,effective_from,effective_to,is_active",
    )
    .eq("is_active", true)
    .order("country_code", { ascending: true })
    .order("category", { ascending: true })
    .order("name", { ascending: true });

  if (error) throw error;
  return (data ?? []).map((row) => ({
    ...row,
    base_cost_usd: Number(row.base_cost_usd ?? 0),
  })) as TariffItem[];
}

export async function mutateTariffItem(
  supabase: SupabaseClient,
  user: GlmUser,
  operation: "insert" | "update" | "deactivate",
  item: Partial<TariffDraft> & { id?: string },
) {
  const conversationId = `tarifario-ui-${getAppSessionId()}`;
  const pendingAction = {
    operation,
    tariffItemId: item.id ?? null,
    item: {
      id: item.id ?? null,
      countryCode: item.countryCode,
      country: item.country,
      itemCode: item.itemCode,
      category: item.category,
      name: item.name,
      detail: item.detail ?? null,
      mandatoryRequirement: item.mandatoryRequirement ?? null,
      formatScope: item.formatScope ?? "WM, SP, BF",
      period: item.period ?? null,
      unitType: item.unitType ?? null,
      baseCostUsd: item.baseCostUsd,
      sourceName: item.sourceName ?? "Edición manual desde módulo Tarifario",
      effectiveFrom: item.effectiveFrom ?? new Date().toISOString().slice(0, 10),
    },
    reason: "Cambio realizado manualmente desde el módulo Tarifario del Cotizador WMC",
  };

  const { data: proposal, error: proposalError } = await supabase.rpc(
    "cotizador_wmc_process_my_agent_action",
    {
      p_action: { action: "tariff_change_proposal", pendingAction },
      p_conversation_id: conversationId,
      p_display_name: user.name,
      p_session_id: getAppSessionId(),
    },
  );
  if (proposalError) throw proposalError;
  if (!proposal?.ok || !proposal?.pendingActionId) {
    throw new Error(proposal?.error || "No se pudo preparar el cambio del tarifario.");
  }

  const { data: confirmed, error: confirmError } = await supabase.rpc(
    "cotizador_wmc_process_my_agent_action",
    {
      p_action: {
        action: "confirm_tariff_change",
        pendingActionId: proposal.pendingActionId,
      },
      p_conversation_id: conversationId,
      p_display_name: user.name,
      p_session_id: getAppSessionId(),
    },
  );
  if (confirmError) throw confirmError;
  if (!confirmed?.ok) {
    throw new Error(confirmed?.error || "No se pudo aplicar el cambio del tarifario.");
  }
  return confirmed as Record<string, unknown>;
}

function rowAttachmentToChatAttachment(raw: unknown): ChatAttachment | null {
  if (!raw || typeof raw !== "object") return null;
  const obj = raw as Record<string, unknown>;
  const filename = typeof obj.filename === "string" ? obj.filename : "archivo";
  const mediaType =
    typeof obj.mediaType === "string" ? obj.mediaType : "application/octet-stream";
  const url = typeof obj.url === "string" ? obj.url : "";
  const storagePath = typeof obj.storagePath === "string" ? obj.storagePath : null;
  return { filename, mediaType, url, storagePath };
}

async function renewAttachmentUrls(supabase: SupabaseClient, attachments: ChatAttachment[]) {
  return Promise.all(
    attachments.map(async (attachment) => {
      if (!attachment.storagePath) return attachment;
      const { data, error } = await supabase.storage
        .from(WMC_UPLOAD_BUCKET)
        .createSignedUrl(attachment.storagePath, 60 * 60);
      if (error || !data?.signedUrl) return attachment;
      return { ...attachment, url: data.signedUrl };
    }),
  );
}

export async function refreshMessageAttachmentUrls(
  supabase: SupabaseClient,
  messages: ChatMessage[],
): Promise<ChatMessage[]> {
  return Promise.all(
    messages.map(async (message) => ({
      ...message,
      attachments: await renewAttachmentUrls(supabase, message.attachments),
    })),
  );
}

export async function loadRemoteChats(
  supabase: SupabaseClient,
  user: GlmUser,
): Promise<ChatSession[]> {
  const { data: conversations, error: conversationError } = await supabase
    .from("cotizador_wmc_conversations")
    .select("id,title,created_at,updated_at")
    .eq("user_id", user.id)
    .order("updated_at", { ascending: false })
    .limit(100);
  if (conversationError) throw conversationError;
  if (!conversations?.length) return [];

  const ids = conversations.map((conversation) => conversation.id);
  const { data: rows, error: messagesError } = await supabase
    .from("cotizador_wmc_messages")
    .select("id,conversation_id,role,content,attachments,metadata,created_at")
    .in("conversation_id", ids)
    .order("created_at", { ascending: true });
  if (messagesError) throw messagesError;

  const messageMap = new Map<string, ChatMessage[]>();
  for (const row of rows ?? []) {
    const rawAttachments = Array.isArray(row.attachments) ? row.attachments : [];
    const attachments = rawAttachments
      .map(rowAttachmentToChatAttachment)
      .filter((value): value is ChatAttachment => Boolean(value));
    const renewed = await renewAttachmentUrls(supabase, attachments);
    const metadata = row.metadata && typeof row.metadata === "object" ? row.metadata : {};
    const message: ChatMessage = {
      id: row.id,
      role: row.role as "user" | "assistant",
      text: row.content ?? "",
      attachments: renewed,
      createdAt: new Date(row.created_at).getTime(),
      metadata: metadata as Record<string, unknown>,
    };
    const list = messageMap.get(row.conversation_id) ?? [];
    list.push(message);
    messageMap.set(row.conversation_id, list);
  }

  return conversations.map((conversation) => ({
    id: conversation.id,
    title: conversation.title || "Nuevo chat",
    messages: messageMap.get(conversation.id) ?? [],
    createdAt: new Date(conversation.created_at).getTime(),
    updatedAt: new Date(conversation.updated_at).getTime(),
  }));
}

export async function ensureRemoteConversation(
  supabase: SupabaseClient,
  user: GlmUser,
  chatId: string,
  title: string,
) {
  const now = new Date().toISOString();
  const { error } = await supabase.from("cotizador_wmc_conversations").upsert(
    {
      id: chatId,
      user_id: user.id,
      user_email: user.email.toLowerCase(),
      title: title || "Nuevo chat",
      updated_at: now,
    },
    { onConflict: "id" },
  );
  if (error) throw error;
}

export async function updateRemoteConversationTitle(
  supabase: SupabaseClient,
  user: GlmUser,
  chatId: string,
  messages: ChatMessage[],
  preferredTitle?: string,
) {
  const title = preferredTitle?.trim() || chatTitleFromMessages(messages);
  const { error } = await supabase
    .from("cotizador_wmc_conversations")
    .update({ title, updated_at: new Date().toISOString() })
    .eq("id", chatId)
    .eq("user_id", user.id);
  if (error) throw error;
}

export async function renameRemoteConversation(
  supabase: SupabaseClient,
  user: GlmUser,
  chatId: string,
  title: string,
) {
  const cleanTitle = title.replace(/\s+/g, " ").trim().slice(0, 80);
  if (!cleanTitle) throw new Error("El nombre del chat no puede estar vacío.");

  const { error } = await supabase
    .from("cotizador_wmc_conversations")
    .update({ title: cleanTitle, updated_at: new Date().toISOString() })
    .eq("id", chatId)
    .eq("user_id", user.id);

  if (error) throw error;
}

export async function saveRemoteMessages(
  supabase: SupabaseClient,
  user: GlmUser,
  chatId: string,
  messages: ChatMessage[],
) {
  if (!messages.length) return;
  const payload = messages.map((message) => ({
    id: message.id,
    conversation_id: chatId,
    user_id: user.id,
    role: message.role,
    content: message.text,
    attachments: message.attachments.map((attachment) => ({
      filename: attachment.filename,
      mediaType: attachment.mediaType,
      url: attachment.url,
      storagePath: attachment.storagePath ?? null,
    })),
    metadata: message.metadata ?? {},
    created_at: new Date(message.createdAt).toISOString(),
  }));
  const { error } = await supabase
    .from("cotizador_wmc_messages")
    .upsert(payload, { onConflict: "id" });
  if (error) throw error;
}

export async function deleteStoredChatAttachments(
  supabase: SupabaseClient,
  chat: ChatSession | undefined,
) {
  if (!chat) return;
  const paths = Array.from(
    new Set(
      chat.messages
        .flatMap((message) => message.attachments)
        .map((attachment) => attachment.storagePath)
        .filter((value): value is string => Boolean(value)),
    ),
  );
  if (!paths.length) return;
  const { error } = await supabase.storage.from(WMC_UPLOAD_BUCKET).remove(paths);
  if (error) throw error;
}

export async function deleteRemoteConversation(
  supabase: SupabaseClient,
  user: GlmUser,
  chatId: string,
) {
  const { error } = await supabase
    .from("cotizador_wmc_conversations")
    .delete()
    .eq("id", chatId)
    .eq("user_id", user.id);
  if (error) throw error;
}

export async function uploadAttachments(
  supabase: SupabaseClient,
  user: GlmUser,
  conversationId: string,
  messageId: string,
  files: FileUIPart[],
): Promise<ChatAttachment[]> {
  const limited = files.slice(0, 8);
  const results: ChatAttachment[] = [];

  for (let index = 0; index < limited.length; index += 1) {
    const file = limited[index];
    const filename = file.filename || `archivo-${index + 1}`;
    const mediaType = file.mediaType || "application/octet-stream";
    const url = file.url || "";
    if (!url) continue;

    const response = await fetch(url);
    if (!response.ok) throw new Error(`No se pudo preparar ${filename} para subirlo.`);
    const blob = await response.blob();
    if (blob.size > 25 * 1024 * 1024) {
      throw new Error(`${filename} supera el límite de 25 MB.`);
    }

    const path = `${user.id}/${conversationId}/${messageId}/${Date.now()}-${index}-${safeFileName(filename)}`;
    const { error: uploadError } = await supabase.storage
      .from(WMC_UPLOAD_BUCKET)
      .upload(path, blob, { contentType: mediaType, upsert: false });
    if (uploadError) throw uploadError;

    const { data: signed, error: signedError } = await supabase.storage
      .from(WMC_UPLOAD_BUCKET)
      .createSignedUrl(path, 60 * 60);
    if (signedError || !signed?.signedUrl) {
      throw signedError || new Error(`No se pudo crear acceso temporal para ${filename}.`);
    }

    results.push({ filename, mediaType, url: signed.signedUrl, storagePath: path });
  }

  return results;
}

export async function callWmcAgent(
  supabase: SupabaseClient,
  user: GlmUser,
  input: {
    text: string;
    conversationId: string;
    messageId: string;
    attachments: ChatAttachment[];
  },
  signal?: AbortSignal,
): Promise<AgentResponse> {
  const token = await getAccessToken(supabase);
  const response = await fetch(getChatWebhookUrl(), {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
      text: input.text,
      conversationId: input.conversationId,
      messageId: input.messageId,
      userEmail: user.email,
      userName: user.name,
      appSessionId: getAppSessionId(),
      sentAt: new Date().toISOString(),
      attachments: input.attachments.map((attachment) => ({
        filename: attachment.filename,
        mediaType: attachment.mediaType,
        url: attachment.url,
        storagePath: attachment.storagePath ?? null,
      })),
    }),
    ...(signal ? { signal } : {}),
  });

  let payload: AgentResponse | null = null;
  try {
    payload = (await response.json()) as AgentResponse;
  } catch {
    payload = null;
  }

  if (!response.ok || payload?.ok === false) {
    const message =
      (typeof payload?.reply === "string" && payload.reply) ||
      (typeof payload?.message === "string" && payload.message) ||
      `El agente respondió con HTTP ${response.status}.`;
    throw new Error(message);
  }

  if (!payload) throw new Error("El agente no devolvió una respuesta válida.");
  return payload;
}

export function assistantTextFromAgent(result: AgentResponse) {
  const base = typeof result.reply === "string" && result.reply.trim() ? result.reply.trim() : "Listo.";
  if (!result.artifactUrl) return base;
  const label =
    result.artifactType === "quote"
      ? "Abrir cotización en Google Sheets"
      : result.artifactType === "monthly_glm"
        ? "Abrir mensualización GLM"
        : result.artifactType === "proposal"
          ? "Abrir propuesta en Google Slides"
          : result.artifactType === "image"
            ? "Abrir imagen generada"
            : "Abrir archivo generado";
  return `${base}\n\n[${label}](${result.artifactUrl})`;
}

export function agentMessageMetadata(result: AgentResponse): Record<string, unknown> {
  return {
    action: result.action ?? "chat",
    model: result.model ?? null,
    warnings: Array.isArray(result.warnings) ? result.warnings : [],
    confidence: result.confidence ?? null,
    requiresConfirmation: Boolean(result.requiresConfirmation),
    pendingActionId: result.pendingActionId ?? null,
    artifactUrl: result.artifactUrl ?? null,
    artifactType: result.artifactType ?? null,
    quoteId: result.quoteId ?? null,
  };
}
