import * as MapLibreGL from '@maplibre/maplibre-react-native';
import { AppState, Platform } from 'react-native';
import NetInfo from '@react-native-community/netinfo';
import { MMKV } from 'react-native-mmkv';
import { VECTOR_MAP_HOST } from 'src/constants';
import { addTelemetryEvent } from 'src/utils/telemetry';

const packLastSize = new Map<string, number>();
const packLastUpdatePercent = new Map<string, number>();
const packLastUpdateTime = new Map<string, number>();



const storage = new MMKV();
const OFFLINE_MAPS_KEY = 'offline_maps';
const PENDING_DOWNLOADS_KEY = 'pending_offline_map_downloads';

const progressCallbacks = {};

let pendingDownloads: any = [];
try {
  const pendingDownloadsString = storage.getString(PENDING_DOWNLOADS_KEY);
  pendingDownloads = pendingDownloadsString ? JSON.parse(pendingDownloadsString) : [];
} catch (error) {
  console.error('Error loading pending downloads:', error);
  pendingDownloads = [];
}

const savePendingDownloads = () => {
  storage.set(PENDING_DOWNLOADS_KEY, JSON.stringify(pendingDownloads));
};

const markPackAsCompleted = (packName) => {
  const mapsString = storage.getString(OFFLINE_MAPS_KEY);
  if (!mapsString) return;

  const maps = JSON.parse(mapsString);
  const mapIndex = maps.findIndex((map) => map.id === packName);

  if (mapIndex >= 0) {
    maps[mapIndex].status = 'valid';
    maps[mapIndex].progress = 100;
    maps[mapIndex].isPaused = false;
    maps[mapIndex].updatedAt = Date.now();

    storage.set(OFFLINE_MAPS_KEY, JSON.stringify(maps));

    pendingDownloads = pendingDownloads.filter((name) => name !== packName);
    savePendingDownloads();
  }
};

let appStateSubscription: any = null;

const setupAppStateListener = () => {
  if (appStateSubscription) {
    appStateSubscription.remove();
  }

  appStateSubscription = AppState.addEventListener('change', async (nextAppState) => {
    if (nextAppState === 'active') {
      try {
        const netInfo = await NetInfo.fetch();
        const isWifi = netInfo.type === 'wifi';

        if (isWifi || Platform.OS === 'android') {
          resumeAllPendingDownloads();
        }
      } catch (error) {
        console.error('Error checking network when app came to foreground:', error);
        resumeAllPendingDownloads();
      }
    }
  });
};

setupAppStateListener();

const init = () => {
  MapLibreGL.OfflineManager.setProgressEventThrottle(800);

  resumeAllPendingDownloads();
  cleanupCompletedMaps();
};

const cleanupCompletedMaps = async () => {
  try {
    const mapsString = storage.getString(OFFLINE_MAPS_KEY);
    if (!mapsString) return;

    const maps = JSON.parse(mapsString);
    const inProgressMaps = maps.filter((map) => map.status === 'downloading');

    for (const map of inProgressMaps) {
      const pack = await getPack(map.id);

      if (pack) {
        try {
          const status = await pack.status();

          if (
            status &&
            (status.percentage === 100 ||
              status.completedResourceCount === status.requiredResourceCount)
          ) {
            markPackAsCompleted(map.id);
          }
        } catch (statusError) {
          console.warn(`Pack ${map.id} is invalid, skipping cleanup.`);
        }
      }
    }
  } catch (error) {
    console.error('Error cleaning up completed maps:', error);
  }
};

const subscribeToPackProgress = (packName, progressCallback, errorCallback) => {
  if (progressCallback) {
    progressCallbacks[packName] = progressCallback;
  }

  MapLibreGL.OfflineManager.subscribe(
    packName,
    (pack, status) => {
      if (status.completedResourceCount === status.requiredResourceCount) {
        markPackAsCompleted(packName);
      }

      const percentage =
        status.requiredResourceCount > 0
          ? (status.completedResourceCount / status.requiredResourceCount) * 100
          : 0;

      const isCompleted = status.completedResourceCount === status.requiredResourceCount;
      const lastPercent = packLastUpdatePercent.get(packName) || 0;
      const lastTime = packLastUpdateTime.get(packName) || 0;
      const now = Date.now();

      // Only perform heavy updates (UI, MMKV writes, Telemetry logging) if:
      // 1. Download completed
      // 2. Download progressed by at least 1.0%
      // 3. Or at least 1.5 seconds have passed
      const shouldUpdate = isCompleted || (percentage - lastPercent >= 1.0) || (now - lastTime >= 1500);

      if (shouldUpdate) {
        packLastUpdatePercent.set(packName, percentage);
        packLastUpdateTime.set(packName, now);

        const lastSize = packLastSize.get(packName) || 0;
        const currentSize = status.completedResourceSize || 0;
        const deltaBytes = currentSize - lastSize;

        if (deltaBytes > 0) {
          packLastSize.set(packName, currentSize);
          addTelemetryEvent({
            type: 'map_tile_pack',
            url: `map-pack://${packName}`,
            caller: 'Offline Maps',
            reqSize: 0,
            resSize: deltaBytes,
            status: 200,
            duration: 0
          });
        }

        updateMapMetadata(packName, {
          progress: percentage,
          size: currentSize
        });

        if (progressCallbacks[packName]) {
          progressCallbacks[packName]({
            name: packName,
            percentage,
            completedSize: currentSize,
            completedResourceCount: status.completedResourceCount,
            requiredResourceCount: status.requiredResourceCount
          });
        }
      }
    },
    (error) => {
      console.error(`Error in progress listener for ${packName}:`, error);

      if (errorCallback) {
        errorCallback(error);
      }
    }
  );
};

const createPack = async (options, progressCallback, errorCallback) => {
  try {
    subscribeToPackProgress(options.name, progressCallback, errorCallback);

    if (!pendingDownloads.includes(options.name)) {
      pendingDownloads.push(options.name);
      savePendingDownloads();
    }

    const mapsString = storage.getString(OFFLINE_MAPS_KEY);
    const maps = mapsString ? JSON.parse(mapsString) : [];
    const mapIndex = maps.findIndex((map) => map.id === options.name);

    if (mapIndex >= 0) {
      maps[mapIndex].styleURL = options.styleURL;
      maps[mapIndex].minZoom = options.minZoom;
      maps[mapIndex].maxZoom = options.maxZoom;

      const [[west, south], [east, north]] = options.bounds;
      const bounds = { north, south, east, west };
      maps[mapIndex].bounds = JSON.stringify(bounds);

      storage.set(OFFLINE_MAPS_KEY, JSON.stringify(maps));
    }

    return await MapLibreGL.OfflineManager.createPack(options);
  } catch (error) {
    console.error('Error creating offline pack:', error);

    pendingDownloads = pendingDownloads.filter((name) => name !== options.name);
    savePendingDownloads();

    updateMapMetadata(options.name, { status: 'invalid' });

    throw error;
  }
};

const getPacks = async () => {
  try {
    return await MapLibreGL.OfflineManager.getPacks();
  } catch (error) {
    console.error('Error getting offline packs:', error);
    return [];
  }
};

const getPack = async (name) => {
  try {
    return await MapLibreGL.OfflineManager.getPack(name);
  } catch (error) {
    console.error(`Error getting offline pack ${name}:`, error);
    return null;
  }
};

const deletePack = async (name) => {
  try {
    const pack = await getPack(name);
    if (pack) {
      await MapLibreGL.OfflineManager.unsubscribe(name);

      delete progressCallbacks[name];

      // Delete the pack
      await MapLibreGL.OfflineManager.deletePack(name);
    }

    pendingDownloads = pendingDownloads.filter((downloadName) => downloadName !== name);
    packLastSize.delete(name);
    savePendingDownloads();
  } catch (error) {
    console.error(`Error deleting pack ${name}:`, error);
    throw error;
  }
};

const invalidatePack = async (name) => {
  try {
    await MapLibreGL.OfflineManager.deletePack(name);

    updateMapMetadata(name, { status: 'invalid', progress: 100 });
  } catch (error) {
    console.error(`Error invalidating pack ${name}:`, error);
    throw error;
  }
};

const resumePackDownload = async (name: string, progressCallback?: any, errorCallback?: any) => {
  try {
    let pack = await getPack(name);

    if (progressCallback) {
      progressCallbacks[name] = progressCallback;
    }

    if (pack) {
      try {
        const status = await pack.status();
        if (
          status &&
          (status.percentage === 100 ||
            status.completedResourceCount === status.requiredResourceCount)
        ) {
          markPackAsCompleted(name);
          return;
        }
      } catch (statusError) {
        console.warn(`Pack ${name} status() failed, will re-create.`);
        pack = null;
      }

      if (pack) {
        subscribeToPackProgress(name, progressCallbacks[name], errorCallback);

        try {
          await pack.resume();
        } catch (resumeError) {
          console.warn(`Pack ${name} resume() failed, will re-create.`);
          pack = null;
        }

        if (pack) {
          updateMapMetadata(name, {
            status: 'downloading',
            isPaused: false
          });
          return;
        }
      }
    }

    const mapsString = storage.getString(OFFLINE_MAPS_KEY);
    if (!mapsString) {
      throw new Error('No maps data in storage');
    }

    const maps = JSON.parse(mapsString);
    const mapData = maps.find((m) => m.id === name);

    if (!mapData) {
      throw new Error(`Map ${name} not found in storage`);
    }

    const bounds = mapData.bounds ? JSON.parse(mapData.bounds) : null;
    if (!bounds) {
      throw new Error(`Invalid bounds for pack ${name}`);
    }

    const options = {
      name,
      styleURL: mapData.styleURL || `${VECTOR_MAP_HOST}/nomadmania-maps2025.json`,
      minZoom: mapData.minZoom || 10,
      maxZoom: mapData.maxZoom || 20,
      bounds: [
        [bounds.west, bounds.south],
        [bounds.east, bounds.north]
      ]
    };

    updateMapMetadata(name, {
      status: 'downloading',
      isPaused: false
    });

    await createPack(options, progressCallbacks[name], errorCallback);

    if (!pendingDownloads.includes(name)) {
      pendingDownloads.push(name);
      savePendingDownloads();
    }
  } catch (error) {
    console.error(`Error resuming pack ${name}:`, error);

    updateMapMetadata(name, {
      status: 'invalid',
      error: error.message
    });
  }
};

const resumeAllPendingDownloads = async () => {
  const mapsString = storage.getString(OFFLINE_MAPS_KEY);
  if (!mapsString) return;

  const maps = JSON.parse(mapsString);

  const downloadingMaps = maps.filter((map) => map.status === 'downloading' && !map.isPaused);

  for (const map of downloadingMaps) {
    try {
      await resumePackDownload(map.id);
    } catch (error) {
      console.error(`Error resuming download for map ${map.id}:`, error);
    }
  }
};

export const updateMapMetadata = (id, updates) => {
  try {
    const mapsString = storage.getString(OFFLINE_MAPS_KEY);
    const maps = mapsString ? JSON.parse(mapsString) : [];

    const mapIndex = maps.findIndex((map) => map.id === id);
    if (mapIndex >= 0) {
      maps[mapIndex] = { ...maps[mapIndex], ...updates };
      storage.set(OFFLINE_MAPS_KEY, JSON.stringify(maps));
    }
  } catch (error) {
    console.error(`Error updating map metadata for ${id}:`, error);
  }
};

const cancelPackDownload = async (name) => {
  try {
    await MapLibreGL.OfflineManager.unsubscribe(name);

    delete progressCallbacks[name];

    try {
      await MapLibreGL.OfflineManager.deletePack(name);
    } catch (e) {}

    pendingDownloads = pendingDownloads.filter((downloadName) => downloadName !== name);
    packLastSize.delete(name);
    savePendingDownloads();


    updateMapMetadata(name, {
      status: 'invalid',
      progress: 0,
      isPaused: false
    });
  } catch (error) {
    console.error(`Error canceling pack ${name}:`, error);
    throw error;
  }
};

const updatePack = async (name, progressCallback, errorCallback) => {
  try {
    const mapsString = storage.getString(OFFLINE_MAPS_KEY);
    if (!mapsString) {
      throw new Error('No maps data found in storage');
    }

    const maps = JSON.parse(mapsString);
    const mapData = maps.find((m) => m.id === name);

    if (!mapData) {
      throw new Error(`Map ${name} not found in MMKV`);
    }

    const bounds = mapData.bounds ? JSON.parse(mapData.bounds) : null;
    if (!bounds) {
      throw new Error(`Invalid bounds for map ${name}`);
    }

    try {
      await deletePack(name);
    } catch (error) {
      console.error(`Error deleting pack ${name} before update:`, error);
    }

    const options = {
      name,
      styleURL: mapData.styleURL || `${VECTOR_MAP_HOST}/nomadmania-maps2025.json`,
      minZoom: mapData.minZoom || 10,
      maxZoom: mapData.maxZoom || 14,
      bounds: [
        [bounds.west, bounds.south],
        [bounds.east, bounds.north]
      ]
    };

    if (progressCallback) {
      progressCallbacks[name] = progressCallback;
    }

    updateMapMetadata(name, {
      status: 'downloading',
      progress: 0,
      isPaused: false,
      updatedAt: Date.now()
    });

    await createPack(options, progressCallback, errorCallback);
  } catch (error) {
    console.error(`Error updating pack ${name}:`, error);

    updateMapMetadata(name, {
      status: 'invalid',
      error: error.message
    });

    throw error;
  }
};

export const offlineMapManager = {
  init,
  createPack,
  getPacks,
  getPack,
  deletePack,
  invalidatePack,
  resumePackDownload,
  resumeAllPendingDownloads,
  cancelPackDownload,
  updatePack
};
