"use client";

import { gql } from "@apollo/client";
import { useMutation, useQuery } from "@apollo/client/react";
import {
  ReactNode,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useState,
} from "react";

import { eateryClient } from "./apollo-clients";
import {
  type ShiftEntity,
  type allShiftsQuery,
  createShiftMutation,
} from "./types";

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

export type ScheduleView = "week" | "month";

interface ShiftContextType {
  // Data
  shifts: ShiftEntity[];
  view: ScheduleView;
  setView: (view: ScheduleView) => void;

  // Current date navigation
  currentDate: Date;
  goToNextWeek: () => void;
  goToPrevWeek: () => void;
  goToNextMonth: () => void;
  goToPrevMonth: () => void;
  goToToday: () => void;

  // Shift actions
  registerShift: (
    shiftId: string,
    staffId: string,
    staffName: string,
  ) => Promise<{ success: boolean; error?: string }>;
  unregisterShift: (shiftId: string, staffId: string) => void;
  createShift: (shift: Omit<ShiftEntity, "id">) => void;
  deleteShift: (shiftId: string) => Promise<void>;

  // Helpers
  getShiftsForDate: (date: Date) => ShiftEntity[];
  getShiftsForWeek: (weekStart: Date) => ShiftEntity[];
  getWeekDates: () => Date[];
  hasConflict: (
    date: Date | string,
    startTime: string,
    endTime: string,
    staffId: string,
    excludeShiftId?: string,
  ) => boolean;
  getWeeklyBudget: () => number;
}

// ─── Context ──────────────────────────────────────────────────────────────────

const ShiftContext = createContext<ShiftContextType | undefined>(undefined);

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

function getMonday(d: Date): Date {
  const date = new Date(d);
  const day = date.getDay();
  const diff = date.getDate() - day + (day === 0 ? -6 : 1);
  date.setDate(diff);
  date.setHours(0, 0, 0, 0);
  return date;
}

function formatDate(d: Date): string {
  const y = d.getFullYear();
  const m = (d.getMonth() + 1).toString().padStart(2, "0");
  const day = d.getDate().toString().padStart(2, "0");
  return `${y}-${m}-${day}`;
}

// Dùng local date parts thay vì toISOString() để tránh lệch timezone UTC+7
function toLocalDateISO(d: Date): string {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}T00:00:00.000Z`;
}

// Normalize Date or ISO string to "YYYY-MM-DD" for safe comparison
function dateKey(d: Date | string | undefined): string {
  if (!d) return "";
  if (d instanceof Date) return formatDate(d);
  return (d as string).slice(0, 10);
}

function timeToMinutes(time: string): number {
  const [h, m] = time.split(":").map(Number);
  return h * 60 + m;
}

function timesOverlap(
  start1: string,
  end1: string,
  start2: string,
  end2: string,
): boolean {
  const s1 = timeToMinutes(start1);
  const e1 = timeToMinutes(end1);
  const s2 = timeToMinutes(start2);
  const e2 = timeToMinutes(end2);
  return s1 < e2 && s2 < e1;
}

// ___ GraphQL __________________________________________________________________

const GET_ALL_SHIFTS = gql`
  query allShifts {
    allShifts {
      id
      date
      startTime
      endTime
      maxStaff
      wage
      registeredStaff {
        id
        staffId
      }
    }
  }
`;

const CREATE_SHIFT = gql`
  mutation createShift($shiftInput: CreateShiftInput!) {
    createShift(shiftInput: $shiftInput) {
      id
      date
      startTime
      endTime
      maxStaff
      wage
    }
  }
`;

const DELETE_SHIFT = gql`
  mutation deleteShift($id: String!) {
    deleteShift(id: $id)
  }
`;

const REGISTER_SHIFT = gql`
  mutation registerShift($RegisterShiftInput: RegisterShiftInput) {
    registerShift(registration: $RegisterShiftInput) {
      staffId
    }
  }
`;

// ─── Provider ─────────────────────────────────────────────────────────────────

export function ShiftProvider({ children }: { children: ReactNode }) {
  const [shifts, setShifts] = useState<ShiftEntity[]>([]);
  const [view, setView] = useState<ScheduleView>("week");
  const [currentDate, setCurrentDate] = useState<Date>(new Date(2026, 3, 10)); // April 10, 2026

  const { data, refetch } = useQuery<allShiftsQuery>(GET_ALL_SHIFTS, {
    client: eateryClient,
    fetchPolicy: "network-only",
  });

  const [mutateCreateShift] = useMutation<createShiftMutation>(CREATE_SHIFT, {
    client: eateryClient,
  });

  const [mutateDeleteShift] = useMutation<{ deleteShift: boolean }>(
    DELETE_SHIFT,
    {
      client: eateryClient,
    },
  );

  const [mutateRegisterShift] = useMutation<
    { registerShift: { staffId: string } },
    { RegisterShiftInput: { shiftId: string } }
  >(REGISTER_SHIFT, { client: eateryClient });

  useEffect(() => {
    if (data) setShifts(data.allShifts);
  }, [data]);

  // ── Navigation ──────────────────────────────────────────────────────────

  const goToNextWeek = useCallback(() => {
    setCurrentDate((prev) => {
      const next = new Date(prev);
      next.setDate(next.getDate() + 7);
      return next;
    });
  }, []);

  const goToPrevWeek = useCallback(() => {
    setCurrentDate((prev) => {
      const next = new Date(prev);
      next.setDate(next.getDate() - 7);
      return next;
    });
  }, []);

  const goToNextMonth = useCallback(() => {
    setCurrentDate((prev) => {
      const next = new Date(prev);
      next.setMonth(next.getMonth() + 1);
      return next;
    });
  }, []);

  const goToPrevMonth = useCallback(() => {
    setCurrentDate((prev) => {
      const next = new Date(prev);
      next.setMonth(next.getMonth() - 1);
      return next;
    });
  }, []);

  const goToToday = useCallback(() => {
    setCurrentDate(new Date(2026, 3, 10));
  }, []);

  // ── Query helpers ───────────────────────────────────────────────────────

  const getWeekDates = useCallback((): Date[] => {
    const monday = getMonday(currentDate);
    return Array.from({ length: 7 }, (_, i) => {
      const d = new Date(monday);
      d.setDate(monday.getDate() + i);
      return d;
    });
  }, [currentDate]);

  const getShiftsForDate = useCallback(
    (date: Date): ShiftEntity[] => {
      const key = formatDate(date);
      return shifts.filter((s) => dateKey(s.date) === key);
    },
    [shifts],
  );

  const getShiftsForWeek = useCallback(
    (weekStart: Date): ShiftEntity[] => {
      const dates = Array.from({ length: 7 }, (_, i) => {
        const d = new Date(weekStart);
        d.setDate(weekStart.getDate() + i);
        return d;
      });
      const keys = new Set(dates.map(formatDate));
      return shifts.filter((s) => keys.has(dateKey(s.date)));
    },
    [shifts],
  );

  const hasConflict = useCallback(
    (
      date: Date | string,
      startTime: string,
      endTime: string,
      staffId: string,
      excludeShiftId?: string,
    ): boolean => {
      const key = dateKey(date);
      return shifts.some(
        (s) =>
          dateKey(s.date) === key &&
          s.id !== excludeShiftId &&
          (s.registeredStaff ?? []).some((rs) => rs.id === staffId) &&
          timesOverlap(startTime, endTime, s.startTime, s.endTime),
      );
    },
    [shifts],
  );

  const getWeeklyBudget = useCallback((): number => {
    const weekDates = getWeekDates();
    const weekKeys = new Set(weekDates.map(formatDate));
    return shifts
      .filter(
        (s) =>
          weekKeys.has(dateKey(s.date)) && (s.registeredStaff ?? []).length > 0,
      )
      .reduce(
        (sum, s) => sum + (s.wage ?? 0) * (s.registeredStaff ?? []).length,
        0,
      );
  }, [shifts, getWeekDates]);

  // ── Shift actions ───────────────────────────────────────────────────────

  const registerShift = useCallback(
    async (
      shiftId: string,
      staffId: string,
      staffName: string,
    ): Promise<{ success: boolean; error?: string }> => {
      const shift = shifts.find((s) => s.id === shiftId);
      if (!shift) return { success: false, error: "Ca làm không tồn tại." };

      if ((shift.registeredStaff ?? []).length >= shift.maxStaff) {
        return { success: false, error: "Ca làm đã đủ người." };
      }

      if ((shift.registeredStaff ?? []).some((rs) => rs.id === staffId)) {
        return { success: false, error: "Bạn đã đăng ký ca này rồi." };
      }

      if (
        shift.date &&
        hasConflict(
          shift.date,
          shift.startTime,
          shift.endTime,
          staffId,
          shiftId,
        )
      ) {
        return {
          success: false,
          error: "Xung đột lịch! Bạn đã có ca làm trùng giờ trong ngày này.",
        };
      }

      try {
        await mutateRegisterShift({
          variables: { RegisterShiftInput: { shiftId } },
        });
        refetch();
        return { success: true };
      } catch (err: unknown) {
        const message =
          err instanceof Error ? err.message : "Đăng ký thất bại, thử lại sau.";
        return { success: false, error: message };
      }
    },
    [shifts, hasConflict, mutateRegisterShift, refetch],
  );

  const unregisterShift = useCallback((shiftId: string, staffId: string) => {
    setShifts((prev) =>
      prev.map((s) => {
        if (s.id !== shiftId) return s;
        const updated = (s.registeredStaff ?? []).filter(
          (rs) => rs.id !== staffId,
        );
        return {
          ...s,
          registeredStaff: updated,
        };
      }),
    );
  }, []);

  const createShift = useCallback(
    async (shift: Omit<ShiftEntity, "id">) => {
      try {
        const { data } = await mutateCreateShift({
          variables: {
            shiftInput: {
              date: toLocalDateISO(shift.date ?? new Date()),
              startTime: shift.startTime,
              endTime: shift.endTime,
              maxStaff: shift.maxStaff,
              wage: shift.wage,
            },
          },
        });

        if (data) {
          // Optimistic: dùng date từ server nếu có, fallback sang local
          const serverDate = data.createShift.date
            ? new Date(data.createShift.date as unknown as string)
            : shift.date;
          setShifts((prev) => [
            ...prev,
            {
              ...data.createShift,
              date: serverDate,
              wage: data.createShift.wage ?? shift.wage,
              registeredStaff: [],
            },
          ]);
          // Refetch để đồng bộ với server
          refetch();
        }
      } catch (err) {
        console.error("[createShift] mutation failed:", err);
      }
    },
    [mutateCreateShift, refetch],
  );

  const deleteShift = useCallback(
    async (shiftId: string) => {
      try {
        const { data } = await mutateDeleteShift({
          variables: { id: shiftId },
        });
        if (data?.deleteShift) {
          setShifts((prev) => prev.filter((s) => s.id !== shiftId));
        }
      } catch (err) {
        console.error("[deleteShift] mutation failed:", err);
      }
    },
    [mutateDeleteShift],
  );

  return (
    <ShiftContext.Provider
      value={{
        shifts,
        view,
        setView,
        currentDate,
        goToNextWeek,
        goToPrevWeek,
        goToNextMonth,
        goToPrevMonth,
        goToToday,
        registerShift,
        unregisterShift,
        createShift,
        deleteShift,
        getShiftsForDate,
        getShiftsForWeek,
        getWeekDates,
        hasConflict,
        getWeeklyBudget,
      }}
    >
      {children}
    </ShiftContext.Provider>
  );
}

// ─── Hook ─────────────────────────────────────────────────────────────────────

export function useShift() {
  const context = useContext(ShiftContext);
  if (context === undefined) {
    throw new Error("useShift must be used within a ShiftProvider");
  }
  return context;
}
