import { Animated, Linking, Platform, Text, TouchableOpacity, View } from 'react-native';
import React, { useEffect, useMemo, useRef, useState } 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 { getOnlineStatus, 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 NetInfo from '@react-native-community/netinfo';
import { getFirstDatabase, getSecondDatabase } from '../../../db';
import { LocationPopup, RegionPopup, WarningModal } from '../../../components';

import { styles } from './style';
import {
  calculateMapRegion,
  clusterMarkers,
  filterCandidates,
  filterCandidatesMarkers,
  findRegionInDataset,
  processIconUrl,
  processMarkerData
} from '../../../utils/mapHelpers';
import { getData } from '../../../modules/map/regionData';
import { fetchSeriesData } from '@api/series';
import MarkerItem from './MarkerItem';
import ClusterItem from './ClusterItem';
import {
  ClusterData,
  FeatureCollection,
  ItemSeries,
  MapScreenProps,
  MarkerData,
  Region,
  Series
} from '../../../types/map';
import { MAP_HOST } from 'src/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);
  const isOnline = getOnlineStatus();

  const { mutateAsync } = fetchSeriesData();

  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<MarkerData[]>([]);
  const [clusters, setClusters] = useState<ClusterData | null>(null);
  const [series, setSeries] = useState<Series[] | null>(null);
  const [processedMarkers, setProcessedMarkers] = useState<ItemSeries[]>([]);

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

  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(() => {
    const unsubscribe = NetInfo.addEventListener((state) => {
      setIsConnected(isOnline as boolean);
    });

    return () => unsubscribe();
  }, [isOnline]);

  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 (!isOnline) return;
    const currentZoom = Math.log2(360 / visibleMapArea.latitudeDelta);

    if (cancelTokenRef.current) {
      const clusteredMarkers = clusterMarkers(processedMarkers, currentZoom, setClusters);
      setMarkers(clusteredMarkers as MarkerData[]);

      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
    );

    isOnline && 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);
          const clusteredMarkers = clusterMarkers(allMarkers, currentZoom, setClusters);

          setMarkers(clusteredMarkers as MarkerData[]);
        }
      }
    );
  };

  const renderMarkers = () => {
    if (!markers.length) return null;

    const singleMarkers = markers.filter((feature) => {
      return feature.properties.dbscan !== 'core';
    });

    return (
      <>
        {singleMarkers.map((marker, idx) => {
          const markerSeries = series?.find((s) => s.id === marker.properties.series_id);
          const iconUrl = markerSeries ? processIconUrl(markerSeries.icon) : 'default_icon_url';

          return <MarkerItem marker={marker} iconUrl={iconUrl} key={idx} />;
        })}
        {clusters &&
          Object.entries(clusters).map(([clusterId, data], idx) => (
            <ClusterItem clusterId={clusterId} data={data} key={idx} />
          ))}
      </>
    );
  };

  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 } };
  }) => {
    cancelTokenRef.current = true;
    const { latitude, longitude } = event.nativeEvent.coordinate;
    const point = turf.point([longitude, latitude]);
    setUserAvatars([]);

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

    let foundRegion = findRegionInDataset(dareRegions, point);

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

    if (foundRegion) {
      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);
        });

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

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

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

              const allMarkers = data.items.map(processMarkerData);
              setProcessedMarkers(allMarkers);
            }
          }
        );
      } else {
        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}`}
      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);
    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]);

  return (
    <View style={styles.container}>
      <MapView
        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}
      >
        {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()}
      </MapView>

      <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}
            onMarkVisited={() => {
              if (!token) {
                setIsWarningModalVisible(true);
              } else {
                console.log('Mark as visited');
              }
            }}
          />
        </>
      ) : (
        <>
          <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)}
      />
    </View>
  );
};

export default MapScreen;
