package de.avatic.lcc.service.calculation;

import de.avatic.lcc.model.db.country.DetourIndex;
import de.avatic.lcc.model.db.country.IsoCode;
import de.avatic.lcc.model.db.nodes.Location;
import de.avatic.lcc.model.db.nodes.Node;
import de.avatic.lcc.repositories.DistanceMatrixRepository;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.service.api.DistanceApiService;
import org.springframework.stereotype.Service;

@Service
public class DistanceService {

    private static final double EARTH_RADIUS = 6371.0;
    private final CountryRepository countryRepository;
    private final DistanceApiService distanceApiService;

    public DistanceService(CountryRepository countryRepository, DistanceApiService distanceApiService) {
        this.countryRepository = countryRepository;
        this.distanceApiService = distanceApiService;
    }

    public double getDistance(Integer srcCountryId, Location src, Integer destCountryId, Location dest, boolean fast) {
        if (fast) return getDistanceFast(srcCountryId, src, destCountryId, dest);

        var distance = distanceApiService.getDistance(src, dest);
        if (distance.isEmpty()) return getDistanceFast(srcCountryId, src, destCountryId, dest);

        return distance.map(value -> value/1000).orElse(0);
    }

    public double getDistance(Node src, Node dest, boolean fast) {
        if (fast) return getDistanceFast(src, dest);

        var distance = distanceApiService.getDistance(src, src.isUserNode(), dest, dest.isUserNode());
        if (distance.isEmpty()) return getDistanceFast(src, dest);

        return distance.map(value -> value.getDistance().intValue()/1000).orElse(0);
    }


    public double getDistanceFast(Integer srcCountryId, Location src, Integer destCountryId, Location dest  ) {
        double srcLatitudeRadians = Math.toRadians(src.getLatitude());
        double srcLongitudeRad = Math.toRadians(src.getLongitude());
        double destLatitudeRadians = Math.toRadians(dest.getLatitude());
        double destLongitudeRadians = Math.toRadians(dest.getLongitude());

        double latitudeDifference = destLatitudeRadians - srcLatitudeRadians;
        double longitudeDifference = destLongitudeRadians - srcLongitudeRad;

        double a = Math.sin(latitudeDifference / 2) * Math.sin(latitudeDifference / 2) + Math.cos(srcLatitudeRadians) * Math.cos(destLatitudeRadians) * Math.sin(longitudeDifference / 2) * Math.sin(longitudeDifference / 2);

        IsoCode isoSrc = countryRepository.getById(srcCountryId).orElseThrow().getIsoCode();
        IsoCode isoDest = countryRepository.getById(destCountryId).orElseThrow().getIsoCode();

        return (getDetourIndex(isoSrc, isoDest) * (EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))));
    }

    private double getDistanceFast(Node src, Node dest) {

        double srcLatitudeRadians = Math.toRadians(src.getGeoLat().doubleValue());
        double srcLongitudeRad = Math.toRadians(src.getGeoLng().doubleValue());
        double destLatitudeRadians = Math.toRadians(dest.getGeoLat().doubleValue());
        double destLongitudeRadians = Math.toRadians(dest.getGeoLng().doubleValue());

        double latitudeDifference = destLatitudeRadians - srcLatitudeRadians;
        double longitudeDifference = destLongitudeRadians - srcLongitudeRad;

        double a = Math.sin(latitudeDifference / 2) * Math.sin(latitudeDifference / 2) + Math.cos(srcLatitudeRadians) * Math.cos(destLatitudeRadians) * Math.sin(longitudeDifference / 2) * Math.sin(longitudeDifference / 2);

        return (getDetourIndex(src, dest) * (EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))));
    }

    private double getDetourIndex(Node src, Node dest) {
        DetourIndex srcDetour = DetourIndex.getDetourIndex(countryRepository.getById(src.getCountryId()).orElseThrow().getIsoCode());

        if (src.getCountryId().equals(dest.getCountryId())) return srcDetour.getAverageDetourIndex();

        DetourIndex destDetour = DetourIndex.getDetourIndex(countryRepository.getById(dest.getCountryId()).orElseThrow().getIsoCode());

        return (srcDetour.getAverageDetourIndex() + destDetour.getAverageDetourIndex()) / 2;
    }

    private double getDetourIndex(IsoCode src, IsoCode dest) {
        DetourIndex srcDetour = DetourIndex.getDetourIndex(src);

        if (src.equals(dest)) return srcDetour.getAverageDetourIndex();

        DetourIndex destDetour = DetourIndex.getDetourIndex(dest);

        return (srcDetour.getAverageDetourIndex() + destDetour.getAverageDetourIndex()) / 2;
    }

}
