/**
 * useOfflineMode Hook
 *
 * Manages offline mode state and project download functionality.
 */

import { useState, useEffect, useCallback, useMemo } from 'react';
import axios from 'axios';
import { downloadManager } from '../lib/offline/DownloadManager';
import { StorageManager } from '../lib/offline/StorageManager';
import { OfflineDbManager } from '../lib/offlineDb/OfflineDbManager';
import { downloadEventBus } from '../lib/offline/DownloadEventBus';
import { OFFLINE_CONFIG } from '../config/offline.config';
import { logger } from '../lib/logger';
import type {
  OfflineProject,
  OfflineManifest,
  ProjectOfflineStatus,
  ProjectDownloadProgressEvent,
} from '../types/offline';

interface UseOfflineModeOptions {
  projectId: string | null;
  projectSlug?: string;
  projectName?: string;
  enabled?: boolean;
}

interface UseOfflineModeResult {
  // Status
  isOfflineCapable: boolean;
  isDownloaded: boolean;
  isDownloading: boolean;
  status: ProjectOfflineStatus;
  progress: number;
  downloadedAssets: number;
  totalAssets: number;
  downloadedBytes: number;
  totalBytes: number;
  error: string | null;

  // Actions
  startDownload: () => Promise<void>;
  pauseDownload: () => void;
  resumeDownload: () => void;
  cancelDownload: () => void;
  deleteOfflineData: () => Promise<void>;
  checkForUpdates: () => Promise<boolean>;

  // Info
  projectInfo: OfflineProject | null;
  estimatedSize: number;
  formatSize: (bytes: number) => string;
}

/**
 * Format bytes to human-readable string
 */
const formatBytes = (bytes: number): string => {
  if (bytes === 0) return '0 B';

  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));

  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
};

export function useOfflineMode(
  options: UseOfflineModeOptions,
): UseOfflineModeResult {
  const { projectId, projectSlug, projectName, enabled = true } = options;

  const [projectInfo, setProjectInfo] = useState<OfflineProject | null>(null);
  const [manifest, setManifest] = useState<OfflineManifest | null>(null);
  const [status, setStatus] = useState<ProjectOfflineStatus>('not_downloaded');
  const [progress, setProgress] = useState(0);
  const [downloadedAssets, setDownloadedAssets] = useState(0);
  const [totalAssets, setTotalAssets] = useState(0);
  const [downloadedBytes, setDownloadedBytes] = useState(0);
  const [totalBytes, setTotalBytes] = useState(0);
  const [error, setError] = useState<string | null>(null);
  const [isPaused, setIsPaused] = useState(false);

  // Check if offline mode is supported
  const isOfflineCapable = useMemo(() => {
    if (typeof window === 'undefined') return false;
    return 'serviceWorker' in navigator && 'caches' in window;
  }, []);

  // Load project offline status from IndexedDB
  useEffect(() => {
    if (!projectId || !enabled) return;

    const loadProjectInfo = async () => {
      const info = await OfflineDbManager.getProject(projectId);
      if (info) {
        setProjectInfo(info);
        setStatus(info.status);
        setDownloadedAssets(info.downloadedAssets);
        setTotalAssets(info.totalAssets);
        setDownloadedBytes(info.downloadedSizeBytes);
        setTotalBytes(info.totalSizeBytes);

        if (info.totalAssets > 0) {
          setProgress(
            Math.round((info.downloadedAssets / info.totalAssets) * 100),
          );
        }
      }
    };

    loadProjectInfo();
  }, [projectId, enabled]);

  // Listen for progress events
  useEffect(() => {
    if (!projectId) return;

    const handleProgress = (data: ProjectDownloadProgressEvent) => {
      if (data.projectId !== projectId) return;

      setProgress(data.progress);
      setDownloadedAssets(data.downloadedAssets);
      setTotalAssets(data.totalAssets);
      setDownloadedBytes(data.downloadedBytes);
      setTotalBytes(data.totalBytes);
    };

    return downloadEventBus.on(
      OFFLINE_CONFIG.events.projectDownloadProgress as Parameters<
        typeof downloadEventBus.on
      >[0],
      handleProgress as Parameters<typeof downloadEventBus.on>[1],
    );
  }, [projectId]);

  // Fetch manifest from backend
  const fetchManifest =
    useCallback(async (): Promise<OfflineManifest | null> => {
      if (!projectId) return null;

      try {
        const response = await axios.get(
          `/api/projects/${projectId}/offline-manifest`,
        );
        return response.data;
      } catch (err) {
        logger.error(
          '[useOfflineMode] Failed to fetch manifest:',
          err instanceof Error ? err : { error: err },
        );
        return null;
      }
    }, [projectId]);

  // Start download
  const startDownload = useCallback(async (): Promise<void> => {
    if (!projectId || !enabled) return;

    setError(null);
    setStatus('downloading');
    setIsPaused(false);

    try {
      // Fetch manifest
      const manifestData = await fetchManifest();
      if (!manifestData) {
        throw new Error('Failed to fetch offline manifest');
      }

      setManifest(manifestData);
      setTotalAssets(manifestData.assets.length);
      setTotalBytes(manifestData.totalSizeBytes);

      // Create or update project record
      const projectRecord: OfflineProject = {
        id: projectId,
        slug: projectSlug || '',
        name: projectName || '',
        status: 'downloading',
        totalAssets: manifestData.assets.length,
        downloadedAssets: 0,
        totalSizeBytes: manifestData.totalSizeBytes,
        downloadedSizeBytes: 0,
        version: manifestData.version,
      };
      await OfflineDbManager.upsertProject(projectRecord);
      setProjectInfo(projectRecord);

      // Check storage quota
      const quota = await StorageManager.getStorageQuota();
      if (!quota.canStore(manifestData.totalSizeBytes)) {
        throw new Error('Insufficient storage space');
      }

      // Add all assets to download queue
      let downloadedCount = 0;
      let downloadedSize = 0;

      for (const asset of manifestData.assets) {
        // Check if already downloaded
        const hasAsset = await StorageManager.hasAsset(asset.url);
        if (hasAsset) {
          downloadedCount++;
          downloadedSize += asset.sizeBytes;
          continue;
        }

        await downloadManager.addJob({
          assetId: asset.id,
          projectId,
          url: asset.url,
          filename: asset.filename,
          variantType: asset.variantType,
          assetType: asset.assetType,
          priority:
            asset.assetType === 'image'
              ? 100
              : asset.assetType === 'video'
                ? 50
                : 75,
        });
      }

      // Update initial progress
      setDownloadedAssets(downloadedCount);
      setDownloadedBytes(downloadedSize);

      if (downloadedCount === manifestData.assets.length) {
        // All already downloaded
        setStatus('downloaded');
        setProgress(100);
        await OfflineDbManager.updateProjectStatus(projectId, 'downloaded');
      } else {
        // Track progress
        const trackProgress = async () => {
          const projectAssets =
            await OfflineDbManager.getProjectAssets(projectId);
          const downloaded = projectAssets.length;
          const dlBytes = projectAssets.reduce(
            (sum, a) => sum + a.sizeBytes,
            0,
          );

          setDownloadedAssets(downloaded);
          setDownloadedBytes(dlBytes);

          const prog = Math.round(
            (downloaded / manifestData.assets.length) * 100,
          );
          setProgress(prog);

          await OfflineDbManager.updateProjectProgress(
            projectId,
            downloaded,
            dlBytes,
          );

          downloadEventBus.emitProjectProgress({
            projectId,
            progress: prog,
            downloadedAssets: downloaded,
            totalAssets: manifestData.assets.length,
            downloadedBytes: dlBytes,
            totalBytes: manifestData.totalSizeBytes,
          });

          if (downloaded === manifestData.assets.length) {
            setStatus('downloaded');
            await OfflineDbManager.updateProjectStatus(projectId, 'downloaded');
            downloadEventBus.emitProjectComplete({ projectId });
          }
        };

        // Poll for progress updates
        const progressInterval = setInterval(trackProgress, 1000);

        // Store cleanup reference (could be used for unmount)
        // Note: This is fire-and-forget, caller should use cancelDownload for cleanup
        setTimeout(
          () => {
            // Auto-stop polling after 10 minutes max
            clearInterval(progressInterval);
          },
          10 * 60 * 1000,
        );
      }
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Download failed';
      setError(message);
      setStatus('error');
      await OfflineDbManager.updateProjectStatus(projectId, 'error');
    }
  }, [projectId, projectSlug, projectName, enabled, fetchManifest]);

  // Pause download
  const pauseDownload = useCallback(() => {
    downloadManager.pauseAll();
    setIsPaused(true);
  }, []);

  // Resume download
  const resumeDownload = useCallback(() => {
    downloadManager.resumeAll();
    setIsPaused(false);
  }, []);

  // Cancel download
  const cancelDownload = useCallback(() => {
    if (!projectId) return;

    downloadManager.cancelProjectDownloads(projectId);
    setStatus('not_downloaded');
    setProgress(0);
    setDownloadedAssets(0);
    setDownloadedBytes(0);
    setIsPaused(false);
    setError(null);

    OfflineDbManager.deleteProject(projectId);
    setProjectInfo(null);
  }, [projectId]);

  // Delete offline data
  const deleteOfflineData = useCallback(async () => {
    if (!projectId) return;

    await StorageManager.deleteProjectAssets(projectId);
    await OfflineDbManager.deleteProject(projectId);

    setProjectInfo(null);
    setStatus('not_downloaded');
    setProgress(0);
    setDownloadedAssets(0);
    setTotalAssets(0);
    setDownloadedBytes(0);
    setTotalBytes(0);
  }, [projectId]);

  // Check for updates
  const checkForUpdates = useCallback(async (): Promise<boolean> => {
    if (!projectId || !projectInfo) return false;

    try {
      const latestManifest = await fetchManifest();
      if (!latestManifest) return false;

      if (latestManifest.version !== projectInfo.version) {
        setStatus('outdated');
        await OfflineDbManager.updateProjectStatus(projectId, 'outdated');
        return true;
      }

      return false;
    } catch {
      return false;
    }
  }, [projectId, projectInfo, fetchManifest]);

  // Computed values
  const isDownloaded = status === 'downloaded';
  const isDownloading = status === 'downloading' && !isPaused;
  const estimatedSize = manifest?.totalSizeBytes || totalBytes;

  return {
    isOfflineCapable,
    isDownloaded,
    isDownloading,
    status,
    progress,
    downloadedAssets,
    totalAssets,
    downloadedBytes,
    totalBytes,
    error,
    startDownload,
    pauseDownload,
    resumeDownload,
    cancelDownload,
    deleteOfflineData,
    checkForUpdates,
    projectInfo,
    estimatedSize,
    formatSize: formatBytes,
  };
}
