import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import * as dataSync from '../api/dataSync';
import { isCountryBanned, findBannedCountry } from '../config/bannedCountries';

// UUID generator that works in non-secure contexts (HTTP)
function generateUUID(): string {
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID();
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

export interface SavedLocation {
  id: string;
  name: string;
  latitude: number;
  longitude: number;
  country?: string;
  countryCode?: string;
}

export class BannedCountryError extends Error {
  bannedCountry: string;
  constructor(country: string) {
    super(`Feeld is not available in ${country}. Pick a different location.`);
    this.name = 'BannedCountryError';
    this.bannedCountry = country;
  }
}

export interface LocationState {
  latitude: number;
  longitude: number;
  name?: string;
  country?: string;
  countryCode?: string;
}

interface LocationContextType {
  location: LocationState | null;
  savedLocations: SavedLocation[];
  // Throws BannedCountryError if the LocationState has a banned country/countryCode.
  setLocation: (location: LocationState | null) => void;
  // Throws BannedCountryError if the country is on the Feeld-restricted list.
  saveLocation: (
    name: string,
    lat: number,
    lng: number,
    meta?: { country?: string; countryCode?: string }
  ) => void;
  deleteLocation: (id: string) => void;
  clearLocation: () => void;
}

const LocationContext = createContext<LocationContextType | null>(null);

const STORAGE_KEY = 'feeld_locations';
const CURRENT_LOCATION_KEY = 'feeld_current_location';

// Strip any banned-country entry coming off the wire / off disk — legacy data
// from before the country filter existed should not be re-applied.
const sanitizeCurrent = (loc: LocationState | null | undefined): LocationState | null => {
  if (!loc) return null;
  if (isCountryBanned(loc.country, loc.countryCode)) return null;
  return loc;
};
const sanitizeSaved = (arr: SavedLocation[]): SavedLocation[] =>
  arr.filter((l) => !isCountryBanned(l.country, l.countryCode));

export function LocationProvider({ children }: { children: ReactNode }) {
  const [location, setLocationState] = useState<LocationState | null>(() => {
    const saved = localStorage.getItem(CURRENT_LOCATION_KEY);
    return sanitizeCurrent(saved ? JSON.parse(saved) : null);
  });

  const [savedLocations, setSavedLocations] = useState<SavedLocation[]>(() => {
    const saved = localStorage.getItem(STORAGE_KEY);
    return sanitizeSaved(saved ? JSON.parse(saved) : []);
  });

  const [hasSyncedFromServer, setHasSyncedFromServer] = useState(false);

  // Fetch from server on mount and merge with localStorage
  useEffect(() => {
    const syncFromServer = async () => {
      try {
        const serverData = await dataSync.getAllFromServer();
        if (serverData) {
          // Merge saved locations from server, dropping any banned-country legacy entries
          if (serverData.savedLocations && serverData.savedLocations.length > 0) {
            setSavedLocations(prev => {
              const serverClean = sanitizeSaved(serverData.savedLocations);
              const merged = [...serverClean];
              for (const local of prev) {
                if (!merged.some((s: SavedLocation) => s.id === local.id)) {
                  merged.push(local);
                }
              }
              // Update localStorage with merged data
              localStorage.setItem(STORAGE_KEY, JSON.stringify(merged));
              return merged;
            });
          }

          // Restore current location if we don't have one
          const cleanCurrent = sanitizeCurrent(serverData.currentLocation);
          if (cleanCurrent && !location) {
            setLocationState(cleanCurrent);
            localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCurrent));
          }

          // Restore custom location if we don't have one
          const cleanCustom = sanitizeCurrent(serverData.customLocation);
          if (cleanCustom && !location) {
            setLocationState(cleanCustom);
            localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(cleanCustom));
          }
        }
      } catch (e) {
        console.error('Failed to sync locations from server:', e);
      } finally {
        setHasSyncedFromServer(true);
      }
    };

    syncFromServer();
  }, []);

  useEffect(() => {
    if (location) {
      localStorage.setItem(CURRENT_LOCATION_KEY, JSON.stringify(location));
      dataSync.setData('currentLocation', location);
    } else {
      localStorage.removeItem(CURRENT_LOCATION_KEY);
    }
  }, [location]);

  // Only sync to server after initial load from server is complete
  useEffect(() => {
    if (!hasSyncedFromServer) return;
    localStorage.setItem(STORAGE_KEY, JSON.stringify(savedLocations));
    // Sync to server
    dataSync.setData('savedLocations', savedLocations);
    // Also sync to dedicated saved-locations endpoint for rotation cron
    fetch('/api/saved-locations', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ locations: savedLocations }),
    }).catch(() => {}); // Best-effort
  }, [savedLocations, hasSyncedFromServer]);

  const setLocation = (loc: LocationState | null) => {
    if (loc && isCountryBanned(loc.country, loc.countryCode)) {
      const hit = findBannedCountry(loc.country, loc.countryCode);
      throw new BannedCountryError(hit?.name ?? loc.country ?? loc.countryCode ?? 'this country');
    }
    setLocationState(loc);
    // Sync to server
    if (loc) {
      dataSync.setData('customLocation', loc);
    }
  };

  const saveLocation = (
    name: string,
    latitude: number,
    longitude: number,
    meta?: { country?: string; countryCode?: string }
  ) => {
    if (isCountryBanned(meta?.country, meta?.countryCode)) {
      const hit = findBannedCountry(meta?.country, meta?.countryCode);
      throw new BannedCountryError(hit?.name ?? meta?.country ?? meta?.countryCode ?? 'this country');
    }
    const newLocation: SavedLocation = {
      id: generateUUID(),
      name,
      latitude,
      longitude,
      country: meta?.country,
      countryCode: meta?.countryCode,
    };
    setSavedLocations((prev) => [...prev, newLocation]);
  };

  const deleteLocation = (id: string) => {
    setSavedLocations((prev) => prev.filter((loc) => loc.id !== id));
  };

  const clearLocation = () => {
    setLocationState(null);
  };

  return (
    <LocationContext.Provider
      value={{
        location,
        savedLocations,
        setLocation,
        saveLocation,
        deleteLocation,
        clearLocation,
      }}
    >
      {children}
    </LocationContext.Provider>
  );
}

export function useLocation() {
  const context = useContext(LocationContext);
  if (!context) {
    throw new Error('useLocation must be used within a LocationProvider');
  }
  return context;
}

export interface GeocodeResult {
  lat: number;
  lng: number;
  displayName: string;
  country?: string;
  countryCode?: string; // ISO 3166-1 alpha-2 (uppercase)
}

// Geocoding helper using OpenStreetMap Nominatim (free, no API key needed)
export async function geocodeAddress(address: string): Promise<GeocodeResult | null> {
  try {
    const response = await fetch(
      `https://nominatim.openstreetmap.org/search?format=json&addressdetails=1&q=${encodeURIComponent(address)}&limit=1`,
      {
        headers: {
          'User-Agent': 'FeeldWebApp/1.0',
        },
      }
    );

    const data = await response.json();

    if (data.length > 0) {
      const hit = data[0];
      const addr = hit.address || {};
      return {
        lat: parseFloat(hit.lat),
        lng: parseFloat(hit.lon),
        displayName: hit.display_name,
        country: addr.country,
        countryCode: addr.country_code ? String(addr.country_code).toUpperCase() : undefined,
      };
    }

    return null;
  } catch (error) {
    console.error('Geocoding error:', error);
    return null;
  }
}
