/**
 * Hooks personalizados para el Dashboard de Llamadas
 * Contiene toda la lógica de estado, efectos y operaciones
 */

import { useState, useEffect, useMemo, useDeferredValue, useCallback } from "react";
import type { 
  ExtensionState, 
  FriendlyStatus, 
  ExtensionStatus 
} from "./types";
import type { AmiEvent } from "@/types/ami-events";

/**
 * Helper para convertir valores unknown de AMI a string de forma segura
 */
const toSafeString = (value: unknown): string => {
  if (value === null || value === undefined) return "";
  if (typeof value === "string") return value;
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  return "";
};

/**
 * Interfaces para respuestas de API
 */
interface AmiStatusResponse {
  connected: boolean;
}

interface EndpointDetailsResponse extends EndpointDetailsPayload {
  success: boolean;
  error?: string;
}

interface ApiResponse<T = unknown> {
  success: boolean;
  data?: T;
  error?: string;
}

interface EndpointItem {
  objectname: string;
  devicestate: string;
  aors?: string;
  contacts?: string;
  transport?: string;
  activechannels?: string;
}

interface EndpointsResponse {
  endpoints: EndpointItem[];
}

interface AmiStatusPayload {
  connected: boolean;
}
import { 
  STATUS_POLL_INTERVAL,
  CLOCK_TICK_INTERVAL,
  FRIENDLY_TO_TECH_STATUS_MAP,
  loadPersistedExtensions,
  persistExtensions,
  mapDeviceStateToStatus,
  extractPeerIdFromObjectName,
  extractPeerIdFromChannel,
  extractCallerName,
  isExtension,
  type EndpointDetailsPayload
} from "./utils";

/**
 * Hook para manejar el estado de conexión AMI
 */
export const useAmiConnection = () => {
  const [connected, setConnected] = useState<boolean | null>(null);

  useEffect(() => {
    const fetchStatus = async () => {
      try {
        const response = await fetch("/api/ami/status");
        const data = await response.json() as AmiStatusResponse;
        setConnected(Boolean(data.connected));
      } catch {
        setConnected(null);
      }
    };

    void fetchStatus();
    const interval = setInterval(fetchStatus, STATUS_POLL_INTERVAL);

    return () => clearInterval(interval);
  }, []);

  return connected;
};

/**
 * Hook para manejar el reloj en tiempo real
 */
export const useClock = () => {
  const [now, setNow] = useState<number>(Date.now());

  useEffect(() => {
    const interval = setInterval(() => setNow(Date.now()), CLOCK_TICK_INTERVAL);
    return () => clearInterval(interval);
  }, []);

  return now;
};

/**
 * Hook para manejar el estado de las extensiones con limpieza forzada periódica
 */
export const useExtensions = () => {
  const [extensions, setExtensions] = useState<Record<string, ExtensionState>>({});
  const [isLoading, setIsLoading] = useState(true);
  const [callerIdLoaded, setCallerIdLoaded] = useState<Record<string, boolean>>({});
  const [initialReady, setInitialReady] = useState(false);
  const [lastForceCleanup, setLastForceCleanup] = useState<number>(Date.now());

  // Función para limpiar estados huérfanos
  const forceCleanupOrphanedStates = useCallback(() => {
    console.log("[Force Cleanup] Iniciando limpieza forzada de estados huérfanos");
    
    setExtensions(prev => {
      const next = { ...prev };
      let cleanedCount = 0;
      
      for (const [peerId, extension] of Object.entries(next)) {
        // Si una extensión lleva más de 2 minutos en estado de llamada sin actualizaciones
        const isStuckInCall = ["InUse", "Ringing", "Busy"].includes(extension.status);
        const callAge = extension.callStartedAt ? Date.now() - extension.callStartedAt : 0;
        const ringAge = extension.ringStartedAt ? Date.now() - extension.ringStartedAt : 0;
        
        // Limpiar si lleva más de 2 minutos en llamada o 30 segundos timbrando
        if ((isStuckInCall && callAge > 120000) || (extension.status === "Ringing" && ringAge > 30000)) {
          console.log(`[Force Cleanup] Limpiando ${peerId} - Estado: ${extension.status}, Edad: ${Math.max(callAge, ringAge)}ms`);
          
          next[peerId] = {
            ...extension,
            status: "Registered",
            callerId: undefined,
            connectedLine: undefined,
            channel: undefined,
            ringStartedAt: undefined,
            callStartedAt: undefined,
          };
          cleanedCount++;
        }
      }
      
      if (cleanedCount > 0) {
        console.log(`[Force Cleanup] Limpiados ${cleanedCount} estados huérfanos`);
      }
      
      return next;
    });
    
    setLastForceCleanup(Date.now());
  }, []);

  // Función manual para refrescar todos los estados
  const manualRefresh = useCallback(async () => {
    console.log("[Manual Refresh] Refrescando todos los estados");
    setIsLoading(true);
    
    try {
      const response = await fetch("/api/ami/endpoints");
      const data = await response.json() as EndpointsResponse;
      const items: EndpointItem[] = data.endpoints ?? [];

      setExtensions(prev => {
        const next: Record<string, ExtensionState> = {};
        
        for (const item of items) {
          const peerId = extractPeerIdFromObjectName(item.objectname);
          const activeChannels = Number(item.activechannels ?? 0);
          
          next[peerId] = {
            peer: peerId,
            status: activeChannels === 0 ? mapDeviceStateToStatus(String(item.devicestate ?? "Unknown"), peerId) : prev[peerId]?.status ?? "Unknown",
            details: "",
            aors: item.aors,
            contacts: item.contacts,
            transport: item.transport,
            // Solo mantener info de llamada si hay canales activos
            ...(activeChannels > 0 && prev[peerId] ? {
              callerId: prev[peerId].callerId,
              connectedLine: prev[peerId].connectedLine,
              channel: prev[peerId].channel,
              ringStartedAt: prev[peerId].ringStartedAt,
              callStartedAt: prev[peerId].callStartedAt,
            } : {})
          };
        }
        
        console.log("[Manual Refresh] Estados actualizados:", Object.keys(next).length);
        return next;
      });
    } catch (_error) {
      console.error("[Manual Refresh] Error:", _error);
    } finally {
      setIsLoading(false);
    }
  }, []);

  // Limpieza automática cada 30 segundos
  useEffect(() => {
    const interval = setInterval(() => {
      const timeSinceLastCleanup = Date.now() - lastForceCleanup;
      if (timeSinceLastCleanup > 30000) { // 30 segundos
        forceCleanupOrphanedStates();
      }
    }, 30000);

    return () => clearInterval(interval);
  }, [forceCleanupOrphanedStates, lastForceCleanup]);

  // Cargar endpoints y detalles iniciales
  useEffect(() => {
    const fetchEndpointsAndDetails = async () => {
      try {
        setIsLoading(true);
        const response = await fetch("/api/ami/endpoints");
        const data = await response.json() as EndpointsResponse;
        const items: EndpointItem[] = data.endpoints ?? [];

        // Construir estado base
        const nextExtensions: Record<string, ExtensionState> = { ...extensions };
        const peers: string[] = [];

        for (const item of items) {
          const peerId = extractPeerIdFromObjectName(item.objectname);
          peers.push(peerId);
          
          nextExtensions[peerId] = nextExtensions[peerId] ?? { 
            peer: peerId, 
            status: "Unknown", 
            details: "" 
          };
          
          nextExtensions[peerId].status = mapDeviceStateToStatus(
            String(item.devicestate ?? "Unknown"),
            peerId
          );
          nextExtensions[peerId].aors = item.aors ?? nextExtensions[peerId].aors;
          nextExtensions[peerId].contacts = item.contacts ?? nextExtensions[peerId].contacts;
          nextExtensions[peerId].transport = item.transport ?? nextExtensions[peerId].transport;
        }

        // Mezclar con persistencia local
        const persisted = loadPersistedExtensions();
        for (const [peerId, persistedData] of Object.entries(persisted)) {
          if (!nextExtensions[peerId]) {
            nextExtensions[peerId] = { 
              peer: peerId, 
              status: "Unknown", 
              details: "" 
            } as ExtensionState;
          }
          nextExtensions[peerId] = { 
            ...nextExtensions[peerId], 
            ...persistedData 
          } as ExtensionState;
          if (!peers.includes(peerId)) peers.push(peerId);
        }

        // Cargar detalles de CallerID en paralelo
        const results = await Promise.allSettled(
          peers.map(async (peerId) => {
            try {
              const response = await fetch(
                `/api/ami/endpoint-details?endpoint=${encodeURIComponent(peerId)}`
              );
              const data = await response.json() as EndpointDetailsResponse;
              const name = extractCallerName(data);
              return { 
                peerId, 
                name: name && name !== "-" ? name : undefined 
              };
            } catch {
              return { peerId, name: undefined };
            }
          })
        );

        const loadedMap: Record<string, boolean> = { ...callerIdLoaded };
        for (const result of results) {
          if (result.status === "fulfilled") {
            const { peerId, name } = result.value as { 
              peerId: string; 
              name?: string 
            };
            if (nextExtensions[peerId]) {
              nextExtensions[peerId].callerIdName = name ?? nextExtensions[peerId].callerIdName;
            }
            loadedMap[peerId] = true;
          }
        }

        setExtensions(nextExtensions);
        setCallerIdLoaded(loadedMap);
        setIsLoading(false);
        setInitialReady(true);
      } catch (_error) {
        console.error("Error cargando endpoints/detalles", _error);
        setIsLoading(false);
      }
    };

    void fetchEndpointsAndDetails();
  }, [callerIdLoaded, extensions]);

  // Persistir cambios
  useEffect(() => {
    if (Object.keys(extensions).length > 0) {
      persistExtensions(extensions);
    }
  }, [extensions]);

  // Cargar CallerID para extensiones faltantes
  useEffect(() => {
    const peersToLoad = Object.values(extensions)
      .filter((ext) => 
        isExtension(ext.peer) && 
        !callerIdLoaded[ext.peer] && 
        !ext.callerIdName
      )
      .map((ext) => ext.peer);

    if (peersToLoad.length === 0) return;

    let cancelled = false;

    const loadCallerIds = async () => {
      for (const peerId of peersToLoad) {
        try {
          const response = await fetch(
            `/api/ami/endpoint-details?endpoint=${peerId}`
          );
          const data = await response.json() as EndpointDetailsResponse;
          const name = extractCallerName(data);
          
          if (cancelled) return;
          
          setExtensions((prev) => ({
            ...prev,
            [peerId]: {
              ...prev[peerId],
              callerIdName: name !== "-" ? name : prev[peerId]?.callerIdName
            }
          }));
          
          setCallerIdLoaded((prev) => ({ ...prev, [peerId]: true }));
        } catch {
          setCallerIdLoaded((prev) => ({ ...prev, [peerId]: true }));
        }
      }
    };

    void loadCallerIds();

    return () => {
      cancelled = true;
    };
  }, [extensions, callerIdLoaded]);

  return {
    extensions,
    setExtensions,
    isLoading,
    initialReady,
    forceCleanupOrphanedStates,
    manualRefresh
  };
};

/**
 * Hook para manejar eventos SSE de AMI
 */
export const useAmiEvents = (
  setExtensions: React.Dispatch<React.SetStateAction<Record<string, ExtensionState>>>,
  setConnected: React.Dispatch<React.SetStateAction<boolean | null>>
) => {
  useEffect(() => {
    const eventSource = new EventSource("/api/ami/events");

    eventSource.onmessage = (event: MessageEvent<string>) => {
      try {
        const amiEvent = JSON.parse(event.data) as AmiEvent;
        
        setExtensions((prev) => {
          const next = { ...prev };

          // Logging para diagnosticar eventos
          console.log(`[AMI Event] ${amiEvent.event}:`, {
            event: amiEvent.event,
            channel: amiEvent.channel,
            peer: amiEvent.peer,
            peerstatus: amiEvent.peerstatus,
            dialstatus: amiEvent.dialstatus,
            activechannels: amiEvent.activechannels,
            devicestate: amiEvent.devicestate,
            objectname: amiEvent.objectname
          });

          // Función auxiliar para verificar si un estado es de llamada activa
          const isActiveCallState = (status: ExtensionStatus) => {
            return status === "InUse" || status === "Ringing" || status === "Busy" || status === "Hold";
          };

          // Manejar evento PeerStatus
          if (amiEvent.event === "PeerStatus" && amiEvent.channeltype === "PJSIP") {
            const peerId = toSafeString(amiEvent.peer).replace("PJSIP/", "");
            if (!next[peerId]) {
              next[peerId] = { peer: peerId, status: "Unknown", details: "" };
            }
            
            // Solo actualizar el estado si no hay una llamada activa
            // Esto evita que el estado de "Timbrando" o "En llamada" desaparezca
            if (!isActiveCallState(next[peerId].status)) {
              next[peerId].status = amiEvent.peerstatus as ExtensionStatus;
            }
          }

          // Manejar evento DialBegin
          if (amiEvent.event === "DialBegin") {
            const peerId = extractPeerIdFromChannel(toSafeString(amiEvent.channel));
            if (peerId) {
              next[peerId] = next[peerId] ?? { 
                peer: peerId, 
                status: "Unknown", 
                details: "" 
              };
              next[peerId].status = "Ringing";
              next[peerId].callerId = toSafeString(amiEvent.calleridnum);
              next[peerId].connectedLine = toSafeString(amiEvent.destcalleridnum);
              next[peerId].channel = toSafeString(amiEvent.channel);
              next[peerId].ringStartedAt = Date.now();
              next[peerId].callStartedAt = undefined;
            }
          }

          // Manejar eventos DialState y DialEnd
          if (amiEvent.event === "DialState" || amiEvent.event === "DialEnd") {
            const peerId = extractPeerIdFromChannel(toSafeString(amiEvent.channel));
            if (peerId && next[peerId]) {
              if (amiEvent.dialstatus === "ANSWER") {
                next[peerId].status = "InUse";
                next[peerId].callStartedAt = Date.now();
                next[peerId].ringStartedAt = undefined;
              } else if (amiEvent.dialstatus === "BUSY") {
                next[peerId].status = "Busy";
              } else if (amiEvent.event === "DialEnd") {
                // Solo limpiar el estado cuando la llamada realmente termine
                next[peerId].status = "Registered";
                next[peerId].ringStartedAt = undefined;
                next[peerId].callStartedAt = undefined;
                next[peerId].callerId = undefined;
                next[peerId].connectedLine = undefined;
                next[peerId].channel = undefined;
              }
              next[peerId].channel = toSafeString(amiEvent.channel) || next[peerId].channel;
            }
          }

          // Manejar evento BridgeEnter
          if (amiEvent.event === "BridgeEnter") {
            const peerId = extractPeerIdFromChannel(toSafeString(amiEvent.channel));
            if (peerId && next[peerId]) {
              next[peerId].status = "InUse";
              next[peerId].callStartedAt = next[peerId].callStartedAt ?? Date.now();
              next[peerId].ringStartedAt = undefined;
            }
          }

          // Manejar eventos Hold
          if (amiEvent.event === "Hold" || amiEvent.event === "MusicOnHoldStart") {
            const peerId = extractPeerIdFromChannel(toSafeString(amiEvent.channel));
            if (peerId && next[peerId]) {
              next[peerId].status = "Hold";
            }
          }

          // Manejar eventos Unhold
          if (amiEvent.event === "Unhold" || amiEvent.event === "MusicOnHoldStop") {
            const peerId = extractPeerIdFromChannel(toSafeString(amiEvent.channel));
            if (peerId && next[peerId]) {
              next[peerId].status = "InUse";
            }
          }

          // Manejar evento Hangup - Este es el evento definitivo para limpiar el estado
          if (amiEvent.event === "Hangup") {
            const peerId = extractPeerIdFromChannel(toSafeString(amiEvent.channel));
            console.log(`[Hangup Event] Limpiando estado para peer: ${peerId}`, {
              channel: amiEvent.channel,
              cause: amiEvent.cause,
              causeTxt: amiEvent.causetxt
            });
            
            if (peerId && next[peerId]) {
              // Limpiar completamente el estado de la llamada
              next[peerId].status = "Registered";
              next[peerId].callerId = undefined;
              next[peerId].connectedLine = undefined;
              next[peerId].channel = undefined;
              next[peerId].ringStartedAt = undefined;
              next[peerId].callStartedAt = undefined;
              
              console.log(`[Hangup Event] Estado limpiado para ${peerId}:`, next[peerId]);
            }
          }

          // Manejar evento EndpointList
          if (amiEvent.event === "EndpointList") {
            const peerId = extractPeerIdFromObjectName(
              toSafeString(amiEvent.objectname)
            );
            if (!peerId) return next;
            
            const deviceState = toSafeString(amiEvent.devicestate) || "Unknown";
            const activeChannels = Number(amiEvent.activechannels ?? 0);
            
            console.log(`🔍 [EndpointList] ${peerId}:`, {
              deviceState,
              activeChannels,
              currentStatus: next[peerId]?.status,
              transport: amiEvent.transport,
              identify: amiEvent.identify,
              raw: amiEvent
            });
            
            next[peerId] = next[peerId] ?? { 
              peer: peerId, 
              status: "Unknown", 
              details: "" 
            };
            
            console.log(`📊 Processing ${peerId}: deviceState=${deviceState}, activeChannels=${activeChannels}`);
            
            // Si no hay canales activos, siempre actualizar el estado basado en devicestate
            // Esto corrige el problema donde extensiones se quedan "En llamada" cuando ya no tienen llamadas
            if (activeChannels === 0) {
              const newStatus = mapDeviceStateToStatus(deviceState, peerId);
              console.log(`🔄 [EndpointList] ${peerId} - Sin canales activos, cambiando de ${next[peerId].status} a ${newStatus}`);
              
              next[peerId].status = newStatus;
              // Limpiar información de llamada si no hay canales activos
              next[peerId].callerId = undefined;
              next[peerId].connectedLine = undefined;
              next[peerId].channel = undefined;
              next[peerId].ringStartedAt = undefined;
              next[peerId].callStartedAt = undefined;
            } else if (!isActiveCallState(next[peerId].status)) {
              // Solo actualizar si no hay un estado activo de llamada y hay canales activos
              const newStatus = mapDeviceStateToStatus(deviceState, peerId);
              console.log(`✏️ [EndpointList] ${peerId} - Con canales activos (${activeChannels}), estado actual no activo, cambiando a ${newStatus}`);
              next[peerId].status = newStatus;
            } else {
              console.log(`➕ [EndpointList] ${peerId} - Manteniendo estado activo ${next[peerId].status} (canales: ${activeChannels})`);
            }
            
            next[peerId].aors = toSafeString(amiEvent.aors) || next[peerId].aors;
            next[peerId].contacts = toSafeString(amiEvent.contacts) || next[peerId].contacts;
            next[peerId].transport = toSafeString(amiEvent.transport) || next[peerId].transport;
          }

          return next;
        });
      } catch (error) {
        console.error("Error parseando evento SSE", error);
      }
    };

    eventSource.addEventListener("ami-status", (event: MessageEvent<string>) => {
      try {
        const payload = JSON.parse(event.data) as AmiStatusPayload;
        setConnected(Boolean(payload.connected));
      } catch {
        // Silenciar errores de parsing
      }
    });

    eventSource.onerror = () => {
      eventSource.close();
    };

    return () => {
      eventSource.close();
    };
  }, [setExtensions, setConnected]);
};

/**
 * Hook para manejar filtros de búsqueda y estado
 */
export const useFilters = (extensions: Record<string, ExtensionState>) => {
  const [searchQuery, setSearchQuery] = useState("");
  const [statusFilter, setStatusFilter] = useState<FriendlyStatus[]>([]);
  const deferredQuery = useDeferredValue(searchQuery);

  const allExtensions = useMemo(
    () => Object.values(extensions).sort((a, b) => a.peer.localeCompare(b.peer)),
    [extensions]
  );

  const filteredExtensions = useMemo(() => {
    const query = (deferredQuery ?? searchQuery).toLowerCase().trim();
    
    return allExtensions.filter((ext) => {
      const matchesQuery = !query || [
        ext.peer,
        ext.callerId,
        ext.connectedLine
      ]
        .filter(Boolean)
        .some((value) => String(value).toLowerCase().includes(query));

      const matchesStatus = statusFilter.length === 0 || 
        statusFilter.some((label) => 
          FRIENDLY_TO_TECH_STATUS_MAP[label].includes(ext.status)
        );

      return matchesQuery && matchesStatus;
    });
  }, [allExtensions, deferredQuery, searchQuery, statusFilter]);

  const toggleStatusFilter = (status: FriendlyStatus) => {
    setStatusFilter((prev) =>
      prev.includes(status)
        ? prev.filter((s) => s !== status)
        : [...prev, status]
    );
  };

  return {
    searchQuery,
    setSearchQuery,
    statusFilter,
    setStatusFilter,
    toggleStatusFilter,
    allExtensions,
    filteredExtensions
  };
};

/**
 * Hook para manejar detalles de endpoints
 */
export const useEndpointDetails = () => {
  const [detailsPeer, setDetailsPeer] = useState<string | null>(null);
  const [endpointDetails, setEndpointDetails] = useState<EndpointDetailsResponse | null>(null);
  const [loadingDetails, setLoadingDetails] = useState(false);

  useEffect(() => {
    const loadDetails = async () => {
      if (!detailsPeer) return;
      
      try {
        setLoadingDetails(true);
        setEndpointDetails(null);
        
        const response = await fetch(
          `/api/ami/endpoint-details?endpoint=${detailsPeer}`
        );
        const data = await response.json() as EndpointDetailsResponse;
        
        if (data?.success) {
          setEndpointDetails(data);
        } else {
          setEndpointDetails({ 
            success: false,
            error: data?.error ?? "No se pudo obtener detalles" 
          });
        }
      } catch {
        setEndpointDetails({ success: false, error: "Error al cargar detalles" });
      } finally {
        setLoadingDetails(false);
      }
    };

    void loadDetails();
  }, [detailsPeer]);

  return {
    detailsPeer,
    setDetailsPeer,
    endpointDetails,
    loadingDetails
  };
};

/**
 * Hook para operaciones de llamadas (originate, hangup)
 */
export const useCallOperations = () => {
  const originateCall = async (peer: string, destNumber: string) => {
    if (!destNumber) return;
    
    const params = {
      channel: `PJSIP/${peer}`,
      context: "default",
      exten: destNumber,
      priority: 1,
      callerid: peer,
    };
    
    const response = await fetch("/api/ami/action", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "Originate", params }),
    });
    
    const data = await response.json() as ApiResponse;
    if (!data.success) {
      console.error("Originate failed", data.error);
    }
  };

  const hangupChannel = async (channel: string | undefined) => {
    if (!channel) return;
    
    const response = await fetch("/api/ami/action", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "Hangup", params: { channel } }),
    });
    
    const data = await response.json() as ApiResponse;
    if (!data.success) {
      console.error("Hangup failed", data.error);
    }
  };

  return {
    originateCall,
    hangupChannel
  };
};

/**
 * Hook para manejar la latencia de endpoints
 */
export const useEndpointsLatency = () => {
  const [latencyData, setLatencyData] = useState<Record<string, { latencyMs?: number; status: string }>>({});
  const [isLoadingLatency, setIsLoadingLatency] = useState(false);

  const fetchLatency = async () => {
    try {
      setIsLoadingLatency(true);
      const response = await fetch("/api/ami/endpoints-latency");
      const data = await response.json() as ApiResponse<Record<string, { latencyMs?: number; status: string }>>;
      
      if (data.success && data.data) {
        setLatencyData(data.data);
      }
    } catch (error) {
      console.error("Error obteniendo latencia de endpoints:", error);
    } finally {
      setIsLoadingLatency(false);
    }
  };

  // Obtener latencia inicial
  useEffect(() => {
    void fetchLatency();
  }, []);

  // Actualizar latencia cada 30 segundos
  useEffect(() => {
    const interval = setInterval(fetchLatency, 30000);
    return () => clearInterval(interval);
  }, []);

  return {
    latencyData,
    isLoadingLatency,
    refreshLatency: fetchLatency
  };
};