"use client";

import { useCallback, useEffect, useState } from "react";
import { createClient, getBrowserUserId } from "@/lib/supabase/client";
import type {
  CalendarEvent,
  EventParticipant,
  EventResponseStatus,
  Profile,
  Task,
} from "@/types";
import { useWorkspaceStore } from "@/stores/workspace-store";
import { logTeamAudit } from "@/lib/team-audit";
import { emitWebhookEvent } from "@/lib/developers/client";

export interface CalendarItem {
  id: string;
  title: string;
  start: Date;
  end: Date;
  allDay: boolean;
  color: string | null;
  type: "event" | "task";
  participantCount?: number;
}

export interface ParticipantAvailability {
  user_id: string;
  label: string;
  busy: boolean;
  conflicts: Array<{
    id: string;
    title: string;
    start_time: string;
    end_time: string;
  }>;
}

export interface SuggestedMeetingSlot {
  start: string;
  end: string;
  availableCount: number;
  unavailableCount: number;
}

export function useEvents(rangeStart: Date, rangeEnd: Date) {
  const [events, setEvents] = useState<CalendarEvent[]>([]);
  const [taskItems, setTaskItems] = useState<Task[]>([]);
  const [loading, setLoading] = useState(true);
  const [userId, setUserId] = useState<string | null>(null);
  const supabase = createClient();
  const activeTeamId = useWorkspaceStore((state) => state.activeTeamId);
  const initialized = useWorkspaceStore((state) => state.initialized);
  const rangeStartIso = rangeStart.toISOString();
  const rangeEndIso = rangeEnd.toISOString();

  useEffect(() => {
    void getBrowserUserId().then(setUserId).catch(() => setUserId(null));
  }, []);

  const fetchEvents = useCallback(async () => {
    if (!initialized) return;

    let query = supabase
      .from("events")
      .select("*")
      .gte("start_time", rangeStartIso)
      .lte("start_time", rangeEndIso)
      .order("start_time");

    query = activeTeamId
      ? query.eq("team_id", activeTeamId)
      : query.is("team_id", null);

    const { data } = await query;
    const rawEvents = (data || []) as CalendarEvent[];

    if (rawEvents.length === 0) {
      setEvents([]);
      return;
    }

    const eventIds = rawEvents.map((event) => event.id);
    const { data: participantRows } = await supabase
      .from("event_participants")
      .select("event_id, user_id, response_status, created_at")
      .in("event_id", eventIds);

    const participantIds = Array.from(
      new Set((participantRows || []).map((row) => row.user_id))
    ) as string[];

    let profilesById = new Map<string, Profile>();
    if (participantIds.length > 0) {
      const { data: profileRows } = await supabase
        .from("profiles")
        .select("id, email, full_name, avatar_url, created_at, updated_at")
        .in("id", participantIds);

      profilesById = new Map(
        ((profileRows || []) as Profile[]).map((profile) => [profile.id, profile])
      );
    }

    const participantsByEvent = new Map<string, EventParticipant[]>();
    (participantRows || []).forEach((row) => {
      const profile = profilesById.get(row.user_id);
      if (!profile) return;
      const list = participantsByEvent.get(row.event_id) || [];
      list.push({
        event_id: row.event_id,
        user_id: row.user_id,
        response_status: row.response_status as EventResponseStatus,
        created_at: row.created_at as string,
        profile,
      });
      participantsByEvent.set(row.event_id, list);
    });

    setEvents(
      rawEvents.map((event) => ({
        ...event,
        participants: participantsByEvent.get(event.id) || [],
      }))
    );
  }, [activeTeamId, initialized, rangeEndIso, rangeStartIso, supabase]);

  const fetchTasks = useCallback(async () => {
    if (!initialized) return;

    let query = supabase
      .from("tasks")
      .select("*")
      .not("deadline", "is", null)
      .gte("deadline", rangeStartIso)
      .lte("deadline", rangeEndIso)
      .is("parent_task_id", null)
      .order("deadline");

    query = activeTeamId
      ? query.eq("team_id", activeTeamId)
      : query.is("team_id", null);

    const { data } = await query;
    setTaskItems(data || []);
  }, [activeTeamId, initialized, rangeEndIso, rangeStartIso, supabase]);

  useEffect(() => {
    if (!initialized) return;

    async function load() {
      setLoading(true);
      await Promise.all([fetchEvents(), fetchTasks()]);
      setLoading(false);
    }
    void load();
  }, [fetchEvents, fetchTasks, initialized]);

  // Merge events + tasks into unified items
  const items: CalendarItem[] = [
    ...events.map((e): CalendarItem => ({
      id: e.id,
      title: e.title,
      start: new Date(e.start_time),
      end: new Date(e.end_time),
      allDay: e.all_day,
      color: e.color,
      type: "event",
      participantCount: e.participants?.length || 0,
    })),
    ...taskItems.map((t): CalendarItem => ({
      id: `task-${t.id}`,
      title: t.title,
      start: new Date(t.deadline!),
      end: new Date(t.deadline!),
      allDay: true,
      color: t.status === "done" ? "#22c55e" : "#6366f1",
      type: "task",
    })),
  ];

  async function createEvent(input: {
    title: string;
    description?: string;
    start_time: string;
    end_time: string;
    all_day: boolean;
    color?: string | null;
    participant_ids?: string[];
  }) {
    if (!userId) throw new Error("Not authenticated");

    const { participant_ids = [], ...eventInput } = input;

    const { data, error } = await supabase
      .from("events")
      .insert({ ...eventInput, user_id: userId, team_id: activeTeamId })
      .select()
      .single();
    if (error) throw error;

    if (participant_ids.length > 0) {
      const { error: participantError } = await supabase
        .from("event_participants")
        .insert(
          participant_ids.map((participantId) => ({
            event_id: data.id,
            user_id: participantId,
            response_status: participantId === userId ? "accepted" : "pending",
          }))
        );

      if (participantError) throw participantError;
    }

    const newParticipantIds = participant_ids.filter((participantId) => participantId !== userId);
    if (newParticipantIds.length > 0) {
      await supabase.from("notifications").insert(
        newParticipantIds.map((participantId) => ({
          user_id: participantId,
          actor_id: userId,
          team_id: activeTeamId,
          kind: "event_invite",
          title: `You were added to ${data.title}`,
          body: eventInput.description || "",
          link: "/calendar",
          details: {
            event_id: data.id,
          },
        }))
      );
    }

    await logTeamAudit(supabase, {
      teamId: activeTeamId,
      actorId: userId,
      action: "event_created",
      entityType: "event",
      entityId: data.id,
      title: data.title,
      details: {
        participant_count: participant_ids.length,
      },
    });
    void emitWebhookEvent({
      teamId: activeTeamId,
      eventType: "event.created",
      data: {
        event: data,
        participant_ids,
      },
    });
    await fetchEvents();
    return data as CalendarEvent;
  }

  async function updateEvent(
    id: string,
    input: Partial<{
      title: string;
      description: string | null;
      start_time: string;
      end_time: string;
      all_day: boolean;
      color: string | null;
      participant_ids: string[];
    }>
  ) {
    const { participant_ids, ...eventInput } = input;
    const existing = events.find((event) => event.id === id);
    const { error } = await supabase.from("events").update(eventInput).eq("id", id);
    if (error) throw error;

    if (participant_ids !== undefined) {
      const previousParticipantIds =
        existing?.participants?.map((participant) => participant.user_id) || [];

      await supabase.from("event_participants").delete().eq("event_id", id);

      if (participant_ids.length > 0) {
        const { error: participantError } = await supabase
          .from("event_participants")
          .insert(
            participant_ids.map((participantId) => ({
              event_id: id,
              user_id: participantId,
              response_status:
                previousParticipantIds.includes(participantId) &&
                existing?.participants
                  ? existing.participants.find((participant) => participant.user_id === participantId)
                      ?.response_status || "pending"
                  : participantId === userId
                  ? "accepted"
                  : "pending",
            }))
          );

        if (participantError) throw participantError;
      }

      const newParticipantIds = participant_ids.filter(
        (participantId) =>
          participantId !== userId && !previousParticipantIds.includes(participantId)
      );

      if (newParticipantIds.length > 0) {
        await supabase.from("notifications").insert(
          newParticipantIds.map((participantId) => ({
            user_id: participantId,
            actor_id: userId,
            team_id: activeTeamId,
            kind: "event_invite",
            title: `You were added to ${(eventInput.title as string | undefined) || existing?.title || "an event"}`,
            body:
              (eventInput.description as string | null | undefined) ||
              existing?.description ||
              "",
            link: "/calendar",
            details: {
              event_id: id,
            },
          }))
        );
      }
    }

    await logTeamAudit(supabase, {
      teamId: activeTeamId,
      actorId: userId,
      action: "event_updated",
      entityType: "event",
      entityId: id,
      title: (eventInput.title as string | undefined) || existing?.title || "Event updated",
      details: {
        participant_count:
          participant_ids !== undefined
            ? participant_ids.length
            : existing?.participants?.length || 0,
      },
    });
    const { data: nextEvent } = await supabase.from("events").select("*").eq("id", id).single();
    void emitWebhookEvent({
      teamId: activeTeamId,
      eventType: "event.updated",
      data: {
        event: nextEvent || null,
        previous_event: existing || null,
        participant_ids:
          participant_ids !== undefined
            ? participant_ids
            : existing?.participants?.map((participant) => participant.user_id) || [],
      },
    });
    await fetchEvents();
  }

  async function deleteEvent(id: string) {
    const existing = events.find((event) => event.id === id);
    const { error } = await supabase.from("events").delete().eq("id", id);
    if (error) throw error;
    await logTeamAudit(supabase, {
      teamId: activeTeamId,
      actorId: userId,
      action: "event_deleted",
      entityType: "event",
      entityId: id,
      title: existing?.title || "Event deleted",
    });
    void emitWebhookEvent({
      teamId: activeTeamId,
      eventType: "event.deleted",
      data: {
        event_id: id,
        event: existing || null,
      },
    });
    await fetchEvents();
  }

  async function respondToEvent(eventId: string, responseStatus: EventResponseStatus) {
    if (!userId) throw new Error("Not authenticated");

    const { error } = await supabase
      .from("event_participants")
      .update({ response_status: responseStatus })
      .eq("event_id", eventId)
      .eq("user_id", userId);

    if (error) throw error;
    await fetchEvents();
  }

  async function checkAvailability(input: {
    participantIds: string[];
    start: string;
    end: string;
    excludeEventId?: string | null;
  }) {
    const uniqueParticipantIds = Array.from(new Set(input.participantIds)).filter(Boolean);

    if (uniqueParticipantIds.length === 0) {
      return [] as ParticipantAvailability[];
    }

    let eventsQuery = supabase
      .from("events")
      .select("id, title, start_time, end_time")
      .lt("start_time", input.end)
      .gt("end_time", input.start);

    eventsQuery = activeTeamId
      ? eventsQuery.eq("team_id", activeTeamId)
      : eventsQuery.is("team_id", null);

    if (input.excludeEventId) {
      eventsQuery = eventsQuery.neq("id", input.excludeEventId);
    }

    const { data: overlappingEvents, error: eventsError } = await eventsQuery;
    if (eventsError) throw eventsError;

    const eventRows =
      (overlappingEvents || []) as Array<{
        id: string;
        title: string;
        start_time: string;
        end_time: string;
      }>;

    const eventIds = eventRows.map((event) => event.id);
    const eventMap = new Map(eventRows.map((event) => [event.id, event]));
    let participantRows: Array<{ event_id: string; user_id: string }> = [];

    if (eventIds.length > 0) {
      const { data: participantData, error: participantError } = await supabase
        .from("event_participants")
        .select("event_id, user_id")
        .in("event_id", eventIds)
        .in("user_id", uniqueParticipantIds);

      if (participantError) throw participantError;
      participantRows =
        (participantData || []) as Array<{ event_id: string; user_id: string }>;
    }

    let profileMap = new Map<string, Profile>();
    const { data: profiles, error: profileError } = await supabase
      .from("profiles")
      .select("id, email, full_name, avatar_url, created_at, updated_at")
      .in("id", uniqueParticipantIds);

    if (profileError) throw profileError;
    profileMap = new Map(((profiles || []) as Profile[]).map((profile) => [profile.id, profile]));

    return uniqueParticipantIds.map((participantId) => {
      const conflicts = participantRows
        .filter((row) => row.user_id === participantId)
        .map((row) => eventMap.get(row.event_id))
        .filter(
          (
            event
          ): event is { id: string; title: string; start_time: string; end_time: string } =>
            !!event
        );

      const profile = profileMap.get(participantId);

      return {
        user_id: participantId,
        label: profile?.full_name || profile?.email || "Team member",
        busy: conflicts.length > 0,
        conflicts,
      };
    });
  }

  async function findBestSlots(input: {
    participantIds: string[];
    date: string;
    durationMinutes: number;
    excludeEventId?: string | null;
  }) {
    const startOfDay = new Date(`${input.date}T08:00:00`);
    const endOfDay = new Date(`${input.date}T18:00:00`);
    const slots: SuggestedMeetingSlot[] = [];

    for (
      let cursor = new Date(startOfDay);
      cursor.getTime() + input.durationMinutes * 60 * 1000 <= endOfDay.getTime();
      cursor = new Date(cursor.getTime() + 30 * 60 * 1000)
    ) {
      const slotStart = new Date(cursor);
      const slotEnd = new Date(cursor.getTime() + input.durationMinutes * 60 * 1000);

      const availability = await checkAvailability({
        participantIds: input.participantIds,
        start: slotStart.toISOString(),
        end: slotEnd.toISOString(),
        excludeEventId: input.excludeEventId,
      });

      const unavailableCount = availability.filter((participant) => participant.busy).length;

      slots.push({
        start: slotStart.toISOString(),
        end: slotEnd.toISOString(),
        availableCount: availability.length - unavailableCount,
        unavailableCount,
      });
    }

    return slots
      .sort(
        (a, b) =>
          a.unavailableCount - b.unavailableCount ||
          b.availableCount - a.availableCount ||
          new Date(a.start).getTime() - new Date(b.start).getTime()
      )
      .slice(0, 5);
  }

  return {
    events,
    items,
    loading,
    currentUserId: userId,
    createEvent,
    updateEvent,
    deleteEvent,
    respondToEvent,
    checkAvailability,
    findBestSlots,
    refetch: async () => {
      await Promise.all([fetchEvents(), fetchTasks()]);
    },
  };
}
