import { Animated, Linking, Platform, Text, TouchableOpacity, View } from 'react-native';
import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import MapView, { Geojson, Marker, UrlTile } from 'react-native-maps';
import * as turf from '@turf/turf';
import * as FileSystem from 'expo-file-system';
import * as Location from 'expo-location';
import { storage, StoreType } from '../../../storage';

import MenuIcon from '../../../../assets/icons/menu.svg';
import SearchIcon from '../../../../assets/icons/search.svg';
import RadarIcon from '../../../../assets/icons/radar.svg';
import LocationIcon from '../../../../assets/icons/location.svg';
import CloseSvg from '../../../../assets/icons/close.svg';

import regions from '../../../../assets/geojson/nm2022.json';
import dareRegions from '../../../../assets/geojson/mqp.json';

import { getFirstDatabase, getSecondDatabase, refreshDatabases } from '../../../db';
import { LocationPopup, WarningModal, EditNmModal } from '../../../components';

import { styles } from './style';
import {
  calculateMapRegion,
  filterCandidates,
  filterCandidatesMarkers,
  findRegionInDataset,
  processIconUrl,
  processMarkerData
} from '../../../utils/mapHelpers';
import { getData } from '../../../modules/map/regionData';
import { fetchSeriesData, usePostSetToggleItem } from '@api/series';
import MarkerItem from './MarkerItem';
import ClusterItem from './ClusterItem';
import { FeatureCollection, ItemSeries, MapScreenProps, Region, Series } from '../../../types/map';
import { MAP_HOST } from 'src/constants';
import { useConnection } from 'src/contexts/ConnectionContext';
import ClusteredMapView from 'react-native-map-clustering';
import { fetchUserData, fetchUserDataDare } from '@api/regions';
import RegionPopup from 'src/components/RegionPopup';
import { usePostSetNmRegionMutation } from '@api/myRegions';
import { usePostSetDareRegionMutation } from '@api/myDARE';
import moment from 'moment';
import { qualityOptions } from '../TravelsScreen/utils/constants';

const tilesBaseURL = `${MAP_HOST}/tiles_osm`;
const localTileDir = `${FileSystem.cacheDirectory}tiles/background`;

const gridUrl = `${MAP_HOST}/tiles_nm/grid`;
const localGridDir = `${FileSystem.cacheDirectory}tiles/grid`;

const localVisitedDir = `${FileSystem.cacheDirectory}tiles/user_visited`;

const dareTiles = `${MAP_HOST}/tiles_nm/regions_mqp`;
const localDareDir = `${FileSystem.cacheDirectory}tiles/regions_mqp`;

const AnimatedMarker = Animated.createAnimatedComponent(Marker);

const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
  const userId = storage.get('uid', StoreType.STRING);
  const token = storage.get('token', StoreType.STRING) as string;
  const netInfo = useConnection();

  const { mutateAsync } = fetchSeriesData();
  const { mutateAsync: mutateUserData } = fetchUserData();
  const { mutateAsync: mutateUserDataDare } = fetchUserDataDare();
  const { mutate: updateSeriesItem } = usePostSetToggleItem();
  const { mutate: updateNM } = usePostSetNmRegionMutation();
  const { mutate: updateDARE } = usePostSetDareRegionMutation();
  const visitedTiles = `${MAP_HOST}/tiles_nm/user_visited/${userId}`;
  const mapRef = useRef<MapView>(null);

  const [isConnected, setIsConnected] = useState<boolean | null>(true);
  const [selectedRegion, setSelectedRegion] = useState<FeatureCollection | null>(null);
  const [regionPopupVisible, setRegionPopupVisible] = useState<boolean | null>(false);
  const [regionData, setRegionData] = useState<Region | null>(null);
  const [userAvatars, setUserAvatars] = useState<string[]>([]);
  const [location, setLocation] = useState<Location.LocationObjectCoords | null>(null);
  const [askLocationVisible, setAskLocationVisible] = useState<boolean>(false);
  const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
  const [isWarningModalVisible, setIsWarningModalVisible] = useState<boolean>(false);

  const [markers, setMarkers] = useState<ItemSeries[]>([]);
  const [series, setSeries] = useState<Series[] | null>(null);
  const [processedMarkers, setProcessedMarkers] = useState<ItemSeries[]>([]);
  const [zoomLevel, setZoomLevel] = useState<number>(0);
  const [userData, setUserData] = useState<any>(null);
  const [isEditModalVisible, setIsEditModalVisible] = useState(false);
  const [modalState, setModalState] = useState({
    selectedFirstYear: 2021,
    selectedLastYear: 2021,
    selectedQuality: qualityOptions[2],
    selectedNoOfVisits: 1,
    years: [],
    id: null
  });

  const handleModalStateChange = (updates: { [key: string]: any }) => {
    setModalState((prevState) => ({ ...prevState, ...updates }));
  };

  const handleOpenEditModal = () => {
    handleModalStateChange({
      selectedFirstYear: userData?.first_visit_year,
      selectedLastYear: userData?.last_visit_year,
      selectedQuality:
        qualityOptions.find((quality) => quality.id === userData?.best_visit_quality) ||
        qualityOptions[2],
      selectedNoOfVisits: userData?.no_of_visits || 1,
      id: regionData?.id
    });
    setIsEditModalVisible(true);
  };

  useEffect(() => {
    const currentYear = moment().year();
    let yearSelector: { label: string; value: number }[] = [{ label: 'visited', value: 1 }];
    for (let i = currentYear; i >= 1951; i--) {
      yearSelector.push({ label: i.toString(), value: i });
    }
    handleModalStateChange({ years: yearSelector });
  }, []);

  const cancelTokenRef = useRef(false);
  const currentTokenRef = useRef(0);
  const strokeWidthAnim = useRef(new Animated.Value(2)).current;

  useEffect(() => {
    if (netInfo?.isInternetReachable) {
      setIsConnected(true);
    } else {
      setIsConnected(false);
    }
  }, [netInfo?.isInternetReachable]);

  useEffect(() => {
    Animated.loop(
      Animated.sequence([
        Animated.timing(strokeWidthAnim, {
          toValue: 3,
          duration: 700,
          useNativeDriver: false
        }),
        Animated.timing(strokeWidthAnim, {
          toValue: 2,
          duration: 700,
          useNativeDriver: false
        })
      ])
    ).start();
  }, [strokeWidthAnim]);

  useEffect(() => {
    navigation.setOptions({
      tabBarStyle: {
        display: regionPopupVisible ? 'none' : 'flex',
        position: 'absolute',
        ...Platform.select({
          android: {
            height: 58
          }
        })
      }
    });
  }, [regionPopupVisible, navigation]);

  useEffect(() => {
    (async () => {
      let { status } = await Location.getForegroundPermissionsAsync();
      if (status !== 'granted') {
        return;
      }

      let currentLocation = await Location.getCurrentPositionAsync({});
      setLocation(currentLocation.coords);

      mapRef.current?.animateToRegion(
        {
          latitude: currentLocation.coords.latitude,
          longitude: currentLocation.coords.longitude,
          latitudeDelta: 5,
          longitudeDelta: 5
        },
        1000
      );
    })();
  }, []);

  const findFeaturesInVisibleMapArea = async (visibleMapArea: {
    latitude?: any;
    longitude?: any;
    latitudeDelta: any;
    longitudeDelta?: any;
  }) => {
    if (!isConnected) return;
    const currentZoom = Math.log2(360 / visibleMapArea.latitudeDelta);
    setZoomLevel(currentZoom);

    if (cancelTokenRef.current) {
      setMarkers(processedMarkers);
      return;
    }
    const thisToken = ++currentTokenRef.current;

    if (!regions || !dareRegions) return;

    if (currentZoom < 7) {
      setMarkers([]);

      return;
    }

    const { latitude, longitude, latitudeDelta, longitudeDelta } = visibleMapArea;
    const bbox: turf.BBox = [
      longitude - longitudeDelta / 2,
      latitude - latitudeDelta / 2,
      longitude + longitudeDelta / 2,
      latitude + latitudeDelta / 2
    ];
    const visibleAreaPolygon = turf.bboxPolygon(bbox);
    const regionsFound = filterCandidates(regions, bbox);
    // const daresFound = filterCandidates(dareRegions, bbox);

    const regionIds = regionsFound.map(
      (region: { properties: { id: any } }) => region.properties.id
    );

    isConnected &&
      (await mutateAsync(
        { regions: JSON.stringify(regionIds), token: String(token) },
        {
          onSuccess: (data) => {
            if (thisToken !== currentTokenRef.current) return;

            setSeries(data.series);

            const markersVisible = filterCandidatesMarkers(data.items, visibleAreaPolygon);
            const allMarkers = markersVisible.map(processMarkerData);
            setMarkers(allMarkers);
          }
        }
      ));
  };

  const handleGetLocation = async () => {
    let { status, canAskAgain } = await Location.getForegroundPermissionsAsync();

    if (status === 'granted') {
      getLocation();
    } else if (!canAskAgain) {
      setOpenSettingsVisible(true);
    } else {
      setAskLocationVisible(true);
    }
  };

  const getLocation = async () => {
    let currentLocation = await Location.getCurrentPositionAsync();
    setLocation(currentLocation.coords);

    mapRef.current?.animateToRegion(
      {
        latitude: currentLocation.coords.latitude,
        longitude: currentLocation.coords.longitude,
        latitudeDelta: 5,
        longitudeDelta: 5
      },
      800
    );

    handleClosePopup();
  };

  const handleAcceptPermission = async () => {
    setAskLocationVisible(false);
    let { status, canAskAgain } = await Location.requestForegroundPermissionsAsync();

    if (status === 'granted') {
      getLocation();
    } else if (!canAskAgain) {
      setOpenSettingsVisible(true);
    }
  };

  const handleRegionData = (regionData: Region, avatars: string[]) => {
    setRegionData(regionData);
    setUserAvatars(avatars);
  };

  const handleMapPress = async (event: {
    nativeEvent: { coordinate: { latitude: any; longitude: any }; action?: string };
  }) => {
    if (event.nativeEvent?.action === 'marker-press') return;

    cancelTokenRef.current = true;
    const { latitude, longitude } = event.nativeEvent.coordinate;
    const point = turf.point([longitude, latitude]);

    let db = getSecondDatabase();
    let tableName = 'places';

    let foundRegion = findRegionInDataset(dareRegions, point);

    if (!foundRegion) {
      foundRegion = findRegionInDataset(regions, point);
      db = getFirstDatabase();
      tableName = 'regions';
    }

    if (foundRegion) {
      if (foundRegion.properties?.id === regionData?.id) return;
      const id = foundRegion.properties?.id;

      setSelectedRegion({
        type: 'FeatureCollection',
        features: [
          {
            geometry: foundRegion.geometry,
            properties: {
              ...foundRegion.properties,
              fill: 'rgba(57, 115, 172, 0.2)',
              stroke: '#3973AC'
            },
            type: 'Feature'
          }
        ]
      });

      await getData(db, id, tableName, handleRegionData)
        .then(() => {
          setRegionPopupVisible(true);
        })
        .catch((error) => {
          console.error('Error fetching data', error);
          refreshDatabases();
        });

      const bounds = turf.bbox(foundRegion);
      const region = calculateMapRegion(bounds);

      mapRef.current?.animateToRegion(region, 1000);

      if (tableName === 'regions') {
        await mutateUserData(
          { region_id: +id, token: String(token) },
          {
            onSuccess: (data) => {
              setUserData({ type: 'nm', ...data });
            }
          }
        );
        await mutateAsync(
          { regions: JSON.stringify([id]), token: String(token) },
          {
            onSuccess: (data) => {
              setSeries(data.series);

              const allMarkers = data.items.map(processMarkerData);
              setProcessedMarkers(allMarkers);
            }
          }
        );
      } else {
        await mutateUserDataDare(
          { dare_id: +id, token: String(token) },
          {
            onSuccess: (data) => {
              setUserData({ type: 'dare', ...data });
            }
          }
        );
        setProcessedMarkers([]);
      }
    } else {
      handleClosePopup();
    }
  };

  const renderMapTiles = (url: string, cacheDir: string, zIndex: number, opacity = 1) => (
    <UrlTile
      urlTemplate={`${url}/{z}/{x}/{y}`}
      maximumZ={15}
      maximumNativeZ={13}
      tileCachePath={cacheDir !== localVisitedDir ? `${cacheDir}` : undefined}
      shouldReplaceMapContent
      minimumZ={0}
      offlineMode={!isConnected}
      opacity={opacity}
      zIndex={zIndex}
    />
  );

  function renderGeoJSON() {
    if (!selectedRegion) return null;

    return (
      <Geojson
        geojson={selectedRegion as any}
        fillColor="rgba(57, 115, 172, 0.2)"
        strokeColor="#3973ac"
        strokeWidth={Platform.OS == 'android' ? 3 : 2}
        zIndex={3}
      />
    );
  }

  const handleClosePopup = async () => {
    cancelTokenRef.current = false;

    setRegionPopupVisible(false);
    setMarkers([]);
    setSelectedRegion(null);
    setRegionData(null);
    const boundaries = await mapRef.current?.getMapBoundaries();
    const { northEast, southWest } = boundaries || {};

    const latitudeDelta = (northEast?.latitude ?? 0) - (southWest?.latitude ?? 0);
    const longitudeDelta = (northEast?.longitude ?? 0) - (southWest?.longitude ?? 0);
    const latitude = (southWest?.latitude ?? 0) + latitudeDelta / 2;
    const longitude = (southWest?.longitude ?? 0) + longitudeDelta / 2;

    findFeaturesInVisibleMapArea({ latitude, longitude, latitudeDelta, longitudeDelta });
  };

  const renderedGeoJSON = useMemo(() => renderGeoJSON(), [selectedRegion]);

  const toggleSeries = useCallback(
    async (item: any) => {
      setMarkers((currentMarkers) =>
        currentMarkers.map((marker) =>
          marker.id === item.id ? { ...marker, visited: Number(!marker.visited) as 0 | 1 } : marker
        )
      );

      const itemData = {
        token: token,
        series_id: item.series_id,
        item_id: item.id,
        checked: (!item.visited ? 1 : 0) as 0 | 1,
        double: 0 as 0 | 1
      };

      try {
        updateSeriesItem(itemData);
      } catch (error) {
        console.error('Failed to update series state', error);
      }
    },
    [token, updateSeriesItem]
  );

  const renderMarkers = () => {
    return markers.map((marker, idx) => {
      const coordinate = { latitude: marker.pointJSON[0], longitude: marker.pointJSON[1] };
      const markerSeries = series?.find((s) => s.id === marker.series_id);
      const iconUrl = markerSeries ? processIconUrl(markerSeries.icon) : 'default_icon_url';
      const seriesName = markerSeries ? markerSeries.name : 'Unknown';

      return (
        <MarkerItem
          marker={marker}
          iconUrl={iconUrl}
          key={`${idx} - ${marker.id}`}
          coordinate={coordinate}
          seriesName={seriesName}
          toggleSeries={toggleSeries}
        />
      );
    });
  };

  const handleUpdateNM = useCallback(
    (region: number, first: number, last: number, visits: number, quality: number) => {
      if (!token) {
        setIsWarningModalVisible(true);
        return;
      }
      const updatedNM = {
        ...userData,
        first_visit_year: first,
        last_visit_year: last,
        best_visit_quality: quality,
        no_of_visits: visits,
        visited: visits > 0 ? true : false
      };

      const updatedNMData = {
        token,
        region,
        first,
        last,
        visits,
        quality
      };

      updateNM(updatedNMData);
      updatedNM && setUserData(updatedNM);
    },
    [userData, token]
  );

  const handleUpdateDare = useCallback(
    (region: number, visits: 0 | 1) => {
      if (!token) {
        setIsWarningModalVisible(true);
        return;
      }
      const updatedDARE = { ...userData, visited: visits > 0 ? true : false };

      const updatedDareData = {
        token,
        region,
        visits
      };

      updateDARE(updatedDareData);
      updatedDARE && setUserData(updatedDARE);
    },
    [userData, token]
  );

  return (
    <View style={styles.container}>
      <ClusteredMapView
        initialRegion={{
          latitude: 0,
          longitude: 0,
          latitudeDelta: 180,
          longitudeDelta: 180
        }}
        ref={mapRef}
        showsMyLocationButton={false}
        showsCompass={false}
        zoomControlEnabled={false}
        onPress={handleMapPress}
        style={styles.map}
        mapType={Platform.OS == 'android' ? 'none' : 'standard'}
        maxZoomLevel={15}
        minZoomLevel={0}
        onRegionChangeComplete={findFeaturesInVisibleMapArea}
        minPoints={zoomLevel < 7 ? 0 : 12}
        tracksViewChanges={false}
        renderCluster={(cluster) => <ClusterItem key={cluster.id} cluster={cluster} />}
      >
        {renderedGeoJSON}
        {renderMapTiles(tilesBaseURL, localTileDir, 1)}
        {renderMapTiles(gridUrl, localGridDir, 2)}
        {userId && renderMapTiles(visitedTiles, localVisitedDir, 2, 0.5)}
        {renderMapTiles(dareTiles, localDareDir, 2, 0.5)}
        {location && (
          <AnimatedMarker coordinate={location} anchor={{ x: 0.5, y: 0.5 }}>
            <Animated.View style={[styles.location, { borderWidth: strokeWidthAnim }]} />
          </AnimatedMarker>
        )}
        {markers && renderMarkers()}
      </ClusteredMapView>

      <LocationPopup
        visible={askLocationVisible}
        onClose={() => setAskLocationVisible(false)}
        onAccept={handleAcceptPermission}
        modalText="To use this feature we need your permission to access your location. If you press OK your system will ask you to approve location sharing with NomadMania app."
      />
      <LocationPopup
        visible={openSettingsVisible}
        onClose={() => setOpenSettingsVisible(false)}
        onAccept={() =>
          Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
        }
        modalText="NomadMania app needs location permissions to function properly. Open settings?"
      />

      {regionPopupVisible && regionData ? (
        <>
          <TouchableOpacity
            style={[styles.cornerButton, styles.topLeftButton, styles.closeLeftButton]}
            onPress={handleClosePopup}
          >
            <CloseSvg fill="white" width={13} height={13} />
            <Text style={styles.textClose}>Close</Text>
          </TouchableOpacity>

          <TouchableOpacity
            onPress={handleGetLocation}
            style={[styles.cornerButton, styles.topRightButton, styles.bottomButton]}
          >
            <LocationIcon />
          </TouchableOpacity>

          <RegionPopup
            region={regionData}
            userAvatars={userAvatars}
            userData={userData}
            openEditModal={handleOpenEditModal}
            updateNM={handleUpdateNM}
            updateDare={handleUpdateDare}
            disabled={!token || !isConnected}
          />
        </>
      ) : (
        <>
          <TouchableOpacity style={[styles.cornerButton, styles.topLeftButton]}>
            <MenuIcon />
          </TouchableOpacity>

          <TouchableOpacity style={[styles.cornerButton, styles.topRightButton]}>
            <SearchIcon fill={'#0F3F4F'} />
          </TouchableOpacity>

          <TouchableOpacity
            style={[styles.cornerButton, styles.bottomButton, styles.bottomLeftButton]}
          >
            <RadarIcon />
          </TouchableOpacity>

          <TouchableOpacity
            onPress={handleGetLocation}
            style={[styles.cornerButton, styles.bottomButton, styles.bottomRightButton]}
          >
            <LocationIcon />
          </TouchableOpacity>
        </>
      )}
      <WarningModal
        type={'unauthorized'}
        isVisible={isWarningModalVisible}
        onClose={() => setIsWarningModalVisible(false)}
      />
      <EditNmModal
        isVisible={isEditModalVisible}
        onClose={() => setIsEditModalVisible(false)}
        modalState={modalState}
        updateModalState={handleModalStateChange}
        updateNM={handleUpdateNM}
      />
    </View>
  );
};

export default MapScreen;
