import { useState, useCallback, useRef, useEffect } from 'react';
import axios from 'axios';
import { Shift, WeekInfo } from '../types/shift';
import { DayCoverageAnalysis } from '../components/DayCoverageIndicator';
import { getNextWeek } from '../utils/weekUtils';
import { handleApiError } from '../utils/errorHandler';

const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const PREFETCH_COUNT = 3;

interface CacheEntry {
  shifts: Shift[];
  coverage: Record<string, DayCoverageAnalysis>;
  timestamp: number;
}

const getCacheKey = (year: number, week: number, deptId: number | ''): string =>
  `${year}-${week}-${deptId}`;

const parseResponse = (response: any): { shifts: Shift[]; coverage: Record<string, DayCoverageAnalysis> } => {
  if (response.data.shifts) {
    return { shifts: response.data.shifts, coverage: response.data.coverage || {} };
  }
  return { shifts: response.data, coverage: {} };
};

interface UseShiftManagementOptions {
  weekInfo: WeekInfo;
  selectedDepartment: number | '';
}

interface UseShiftManagementResult {
  shifts: Shift[];
  coverage: Record<string, DayCoverageAnalysis>;
  loading: boolean;
  error: string | null;
  setShifts: React.Dispatch<React.SetStateAction<Shift[]>>;
  loadShifts: () => Promise<void>;
  clearCache: () => void;
  createShift: (shiftData: any) => Promise<Shift | null>;
  updateShift: (shiftId: number, shiftData: any) => Promise<Shift | null>;
  deleteShift: (shiftId: number) => Promise<boolean>;
  duplicateShift: (shift: Shift, targetDate: string) => Promise<Shift | null>;
  bulkConfirmWeek: () => Promise<boolean>;
}

/**
 * Custom hook for shift management with prefetch cache.
 * Caches shift data per week/department and prefetches the next 3 weeks
 * in the background for instant navigation.
 */
export function useShiftManagement({
  weekInfo,
  selectedDepartment,
}: UseShiftManagementOptions): UseShiftManagementResult {
  const [shifts, setShifts] = useState<Shift[]>([]);
  const [coverage, setCoverage] = useState<Record<string, DayCoverageAnalysis>>({});
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Cache: Map<cacheKey, CacheEntry> — persists across renders, no re-render on updates
  const cacheRef = useRef<Map<string, CacheEntry>>(new Map());
  const abortControllerRef = useRef<AbortController | null>(null);
  const prevDeptRef = useRef(selectedDepartment);

  // Clear cache when department changes
  useEffect(() => {
    if (prevDeptRef.current !== selectedDepartment) {
      cacheRef.current.clear();
      prevDeptRef.current = selectedDepartment;
    }
  }, [selectedDepartment]);

  // Cleanup abort controller on unmount
  useEffect(() => {
    return () => {
      abortControllerRef.current?.abort();
    };
  }, []);

  /**
   * Prefetch the next N weeks in the background (sequential, best-effort).
   */
  const prefetchNextWeeks = useCallback((fromYear: number, fromWeek: number, deptId: number | '') => {
    if (!deptId) return;

    // Cancel any running prefetch
    abortControllerRef.current?.abort();
    const controller = new AbortController();
    abortControllerRef.current = controller;

    const prefetch = async () => {
      let year = fromYear;
      let week = fromWeek;

      for (let i = 0; i < PREFETCH_COUNT; i++) {
        if (controller.signal.aborted) return;
        ({ year, week } = getNextWeek(year, week));

        const key = getCacheKey(year, week, deptId);
        const cached = cacheRef.current.get(key);
        if (cached && Date.now() - cached.timestamp < CACHE_TTL) continue;

        try {
          const response = await axios.get('/zeiterfassung/shifts', {
            params: { year, week, ber_id: deptId, include_coverage: 1 },
            signal: controller.signal,
          });
          const data = parseResponse(response);
          cacheRef.current.set(key, { ...data, timestamp: Date.now() });
        } catch (err) {
          if (axios.isCancel(err)) return;
          // Silently fail prefetch
        }
      }
    };

    prefetch();
  }, []);

  /**
   * Load shifts from cache or fetch from server.
   * Called by the useEffect on week/dept change — uses cache when available.
   */
  const loadFromCacheOrFetch = useCallback(async () => {
    if (!selectedDepartment) {
      setShifts([]);
      setCoverage({});
      return;
    }

    const key = getCacheKey(weekInfo.year, weekInfo.week, selectedDepartment);
    const cached = cacheRef.current.get(key);

    // Cache hit — instant display, no loading spinner
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
      setShifts(cached.shifts);
      setCoverage(cached.coverage);
      setError(null);
      prefetchNextWeeks(weekInfo.year, weekInfo.week, selectedDepartment);
      return;
    }

    // Cache miss — fetch from server
    try {
      setLoading(true);
      setError(null);

      const response = await axios.get('/zeiterfassung/shifts', {
        params: {
          year: weekInfo.year,
          week: weekInfo.week,
          ber_id: selectedDepartment,
          include_coverage: 1,
        },
      });

      const data = parseResponse(response);
      setShifts(data.shifts);
      setCoverage(data.coverage);

      // Store in cache
      cacheRef.current.set(key, { ...data, timestamp: Date.now() });

      // Prefetch next weeks
      prefetchNextWeeks(weekInfo.year, weekInfo.week, selectedDepartment);
    } catch (err) {
      const errorMsg = handleApiError('Loading shifts', err);
      setError(errorMsg);
      setShifts([]);
      setCoverage({});
    } finally {
      setLoading(false);
    }
  }, [weekInfo.year, weekInfo.week, selectedDepartment, prefetchNextWeeks]);

  /**
   * Force reload shifts from server (invalidates cache for current week).
   * Called after mutations (create, update, delete, assign, etc.)
   */
  const loadShifts = useCallback(async () => {
    if (!selectedDepartment) {
      setShifts([]);
      setCoverage({});
      return;
    }

    // Invalidate current week's cache
    const key = getCacheKey(weekInfo.year, weekInfo.week, selectedDepartment);
    cacheRef.current.delete(key);

    try {
      setLoading(true);
      setError(null);

      const response = await axios.get('/zeiterfassung/shifts', {
        params: {
          year: weekInfo.year,
          week: weekInfo.week,
          ber_id: selectedDepartment,
          include_coverage: 1,
        },
      });

      const data = parseResponse(response);
      setShifts(data.shifts);
      setCoverage(data.coverage);

      // Re-cache the fresh data
      cacheRef.current.set(key, { ...data, timestamp: Date.now() });
    } catch (err) {
      const errorMsg = handleApiError('Loading shifts', err);
      setError(errorMsg);
      setShifts([]);
      setCoverage({});
    } finally {
      setLoading(false);
    }
  }, [weekInfo.year, weekInfo.week, selectedDepartment]);

  // Auto-load when week or department changes (cache-aware)
  useEffect(() => {
    if (selectedDepartment) {
      loadFromCacheOrFetch();
    }
  }, [weekInfo.week, weekInfo.year, selectedDepartment, loadFromCacheOrFetch]);

  /**
   * Drop every cached week (used after bulk operations that may affect any week).
   */
  const clearCache = useCallback(() => {
    cacheRef.current.clear();
    abortControllerRef.current?.abort();
  }, []);

  /**
   * Create a new shift
   */
  const invalidateCurrentCache = useCallback(() => {
    if (!selectedDepartment) return;
    const key = getCacheKey(weekInfo.year, weekInfo.week, selectedDepartment);
    cacheRef.current.delete(key);
  }, [weekInfo.year, weekInfo.week, selectedDepartment]);

  const createShift = useCallback(async (shiftData: any): Promise<Shift | null> => {
    try {
      setError(null);
      const response = await axios.post('/zeiterfassung/shifts', shiftData);
      const newShift = response.data.shift;

      // Add to local state and invalidate cache
      setShifts((prev) => [...prev, newShift]);
      invalidateCurrentCache();

      return newShift;
    } catch (err) {
      const errorMsg = handleApiError('Creating shift', err);
      setError(errorMsg);
      return null;
    }
  }, [invalidateCurrentCache]);

  /**
   * Update an existing shift
   */
  const updateShift = useCallback(async (
    shiftId: number,
    shiftData: any
  ): Promise<Shift | null> => {
    try {
      setError(null);
      const response = await axios.put(`/zeiterfassung/shifts/${shiftId}`, shiftData);
      const updatedShift = response.data.shift;

      // Update in local state and invalidate cache
      setShifts((prev) =>
        prev.map((s) => (s.zs_id === shiftId ? updatedShift : s))
      );
      invalidateCurrentCache();

      return updatedShift;
    } catch (err) {
      const errorMsg = handleApiError('Updating shift', err);
      setError(errorMsg);
      return null;
    }
  }, [invalidateCurrentCache]);

  /**
   * Delete a shift
   */
  const deleteShift = useCallback(async (shiftId: number): Promise<boolean> => {
    try {
      setError(null);
      await axios.delete(`/zeiterfassung/shifts/${shiftId}`);

      // Remove from local state and invalidate cache
      setShifts((prev) => prev.filter((s) => s.zs_id !== shiftId));
      invalidateCurrentCache();

      return true;
    } catch (err) {
      const errorMsg = handleApiError('Deleting shift', err);
      setError(errorMsg);
      return false;
    }
  }, [invalidateCurrentCache]);

  /**
   * Duplicate a shift to a new date with all assignments
   */
  const duplicateShift = useCallback(async (
    shift: Shift,
    targetDate: string
  ): Promise<Shift | null> => {
    try {
      setError(null);

      // Create new shift with same properties but different date
      const newShiftData = {
        zs_template_id: shift.zs_template_id,
        zs_ber_id: shift.zs_ber_id,
        zs_date: targetDate,
        zs_start_time: shift.zs_start_time,
        zs_end_time: shift.zs_end_time,
        zs_name: shift.zs_name,
        zs_notes: shift.zs_notes,
        zs_color: shift.zs_color,
        zs_status: 'planned',
      };

      const response = await axios.post('/zeiterfassung/shifts', newShiftData);
      const newShift = response.data.shift;

      // Track assignment results
      const assignmentResults = {
        succeeded: 0,
        failed: 0,
        failedUsers: [] as { userId: number; userName?: string; error: string }[],
      };

      // Copy assignments from original shift
      for (const assignment of shift.assigned_users) {
        // Skip unassigned placeholders
        if (!assignment.user_id || assignment.is_unassigned) {
          continue;
        }

        try {
          await axios.post(`/zeiterfassung/shifts/${newShift.zs_id}/assign`, {
            user_id: assignment.user_id,
            role_id: assignment.role_id,
          });
          assignmentResults.succeeded++;
        } catch (assignErr: any) {
          assignmentResults.failed++;
          assignmentResults.failedUsers.push({
            userId: assignment.user_id,
            userName: assignment.user_name,
            error: assignErr.response?.data?.message || 'Assignment failed',
          });
          console.warn(`Could not assign user ${assignment.user_id}:`, assignErr);
        }
      }

      // Report partial failures to user
      if (assignmentResults.failed > 0) {
        const failedNames = assignmentResults.failedUsers
          .map(u => u.userName || `User #${u.userId}`)
          .join(', ');
        const warningMsg = `Shift created but ${assignmentResults.failed} assignment(s) failed: ${failedNames}`;
        setError(warningMsg);
        console.error('[useShiftManagement] Partial failure:', assignmentResults);
      }

      // Reload shifts to get updated data
      await loadShifts();

      return newShift;
    } catch (err) {
      const errorMsg = handleApiError('Duplicating shift', err);
      setError(errorMsg);
      return null;
    }
  }, [loadShifts]);

  /**
   * Bulk confirm all shifts in the current week
   */
  const bulkConfirmWeek = useCallback(async (): Promise<boolean> => {
    try {
      setError(null);

      await axios.post('/zeiterfassung/shifts/bulk-confirm-week', {
        year: weekInfo.year,
        week: weekInfo.week,
        ber_id: selectedDepartment,
      });

      // Reload shifts to reflect status changes
      await loadShifts();

      return true;
    } catch (err) {
      const errorMsg = handleApiError('Bulk confirming shifts', err);
      setError(errorMsg);
      return false;
    }
  }, [weekInfo.year, weekInfo.week, selectedDepartment, loadShifts]);

  return {
    shifts,
    coverage,
    loading,
    error,
    setShifts,
    loadShifts,
    clearCache,
    createShift,
    updateShift,
    deleteShift,
    duplicateShift,
    bulkConfirmWeek,
  };
}
