package com.fsd.hellovelo.service;

//import com.fsd.hellovelo.entity.Bike;
import com.fsd.hellovelo.entity.Bike;
import com.fsd.hellovelo.entity.EStatus;
import com.fsd.hellovelo.entity.Station;
//import com.fsd.hellovelo.repository.BikeRepository;
import com.fsd.hellovelo.exceptions.ForeignKeyConstraintException;
import com.fsd.hellovelo.exceptions.ResourceNotFoundException;
import com.fsd.hellovelo.payload.response.GeocodeResponse;
import com.fsd.hellovelo.repository.BikeRepository;
import com.fsd.hellovelo.repository.StationRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import javax.transaction.Transactional;
import javax.validation.Valid;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class StationService {

    @Autowired
    private StationRepository stationRepository;

    @Autowired
    private BikeRepository bikeRepository;

    public List<Station> findAllStations(){
        List<Station> stations = stationRepository.findStationsByStatusIs(EStatus.ACTIVE);
        for (Station station: stations) {
            station.setAvailableSlots(station.getTotalSlots() - bikeRepository.countAllByStation(station));
            station.setNumberOfBikes(station.getTotalSlots() - station.getAvailableSlots());
        }
        //stationRepository.saveAll(stations);
        return stations;
    }

    public List<Station> findAllStationsUsers(){
        List<Station> stations = stationRepository.findStationsByStatusIsAndIdIsNot(EStatus.ACTIVE, 1L);
        for (Station station: stations) {
            station.setAvailableSlots(station.getTotalSlots() - bikeRepository.countAllByStation(station));
            station.setNumberOfBikes(station.getTotalSlots() - station.getAvailableSlots());
        }
//        stationRepository.saveAll(stations);
        return stations;
    }

    public List<Station> findAllTransferStations(Long stationId) {
//        List<Station> stations = stationRepository.findAll();
        List<Station> stations = stationRepository.findStationsByStatusIsAndIdIsNot(EStatus.ACTIVE, stationId);
        List<Station> transferStations = new ArrayList<>();
        for (Station station: stations) {
            if(station.getTotalSlots() - bikeRepository.countAllByStation(station) != 0) {
                transferStations.add(station);
            }
        }
        return transferStations;
    }

    public Station addNewStation(@Valid Station station) {
        Optional<Station> stationByName = stationRepository.findStationByName(station.getName());
        if(stationByName.isPresent()) {
            throw new IllegalArgumentException("Station name is already taken");
        }
        if(station.getTotalSlots() > 30) {
            throw new IllegalArgumentException("Total bike slots must not exceed 30.");
        }
        if(!coordinatesValid(station.getLatitude(), station.getLongitude())) {
            throw new IllegalArgumentException("Absolute value of latitude must not be greater than 90. The absolute value of longitude must not be greater than 180");
        }
        Optional<Station> stationByLocation = stationRepository.findStationByLatitudeAndLongitude(
                station.getLatitude(), station.getLongitude());
        if(stationByLocation.isPresent()) {
            throw new IllegalArgumentException("Station location is already taken");
        }
        return stationRepository.save(station);
    }

    public void deleteStation(Long stationId) {
        boolean exists = stationRepository.existsById(stationId);
        if (!exists) {
            throw new ResourceNotFoundException("Station does not exist with id: " + stationId);
        }
        if(stationId == 1) {
            throw new IllegalArgumentException("Station with id: 1 cannot be deleted");
        }
        boolean hasBikes = bikeRepository.existsByStation_Id(stationId);
        if(hasBikes) {
            throw new ForeignKeyConstraintException(String.format("Station %d contains a 1 or more bikes and cannot be deleted at the moment.", stationId));
        }
        Station station = stationRepository.findById(stationId).get();
        station.setStatus(EStatus.DISABLED);
        stationRepository.save(station);
    }

    public Station getStation(Long stationId) {
        boolean exists = stationRepository.existsById(stationId);
        if (!exists) {
            throw new ResourceNotFoundException("Station does not exist with id: " + stationId);
        }
        Station station = stationRepository.findById(stationId).get();
        return station;
    }

    public Station getStationWithBikes(Long stationId) {
        boolean exists = stationRepository.existsById(stationId);
        if (!exists) {
            throw new ResourceNotFoundException("Station does not exist with id: " + stationId);
        }
        Station station = stationRepository.findById(stationId).get();
        List <Bike> bikes = bikeRepository.findAllByStationIdAndCurrentlyInUseIsFalseAndStatusIs(stationId, EStatus.ACTIVE);
        for (Bike bike: bikes) {
        }
        station.setBikes(bikes);
        return station;
    }

    public Station getStationUsers(Long stationId) {
        boolean exists = stationRepository.existsById(stationId);
        if (!exists) {
            throw new ResourceNotFoundException("Station does not exist with id: " + stationId);
        }
        Station station = stationRepository.findById(stationId).get();
        if (station.getStatus() == EStatus.DISABLED) {
            throw new ResourceNotFoundException("Station is unavailable with id: " + stationId);
        }
        List <Bike> bikes = bikeRepository.findAllByStationIdAndCurrentlyInUseIsFalseAndStatusIs(stationId, EStatus.ACTIVE);
        station.setBikes(bikes);
        return station;
    }

    public Station updateStation(Long stationId, Station stationDetails) {
        boolean exists = stationRepository.existsById(stationId);
        if (!exists) {
            throw new ResourceNotFoundException("Station does not exist with id: " + stationId);
        }
        Station station = stationRepository.findById(stationId).get();
        if (stationId == 1 && stationDetails.getStatus() == EStatus.DISABLED) {
            System.out.println("in here");
            throw new IllegalArgumentException("Station with id: 1 cannot be disabled ");
        }
        if (!station.getName().equals(stationDetails.getName())) {
            Optional<Station> stationByName = stationRepository.findStationByName(stationDetails.getName());
            if(stationByName.isPresent()) {
                throw new IllegalArgumentException("Station name is already taken");
            }
        }
        if(stationDetails.getTotalSlots() > 30) {
            throw new IllegalArgumentException("Total bike slots must not exceed 30.");
        }
        if(!coordinatesValid(stationDetails.getLatitude(), stationDetails.getLongitude())) {
            throw new IllegalArgumentException("Absolute value of latitude must not be greater than 90. The absolute value of longitude must not be greater than 180");
        }
        if (!station.getLatitude().equals(stationDetails.getLatitude()) && !station.getLongitude().equals(stationDetails.getLongitude())) {
            Optional<Station> stationByLocation = stationRepository.findStationByLatitudeAndLongitude(
                    stationDetails.getLatitude(), stationDetails.getLongitude());
            if(stationByLocation.isPresent()) {
                throw new IllegalArgumentException("Station location is already taken");
            }
        }
        station.setName(stationDetails.getName());
        station.setTotalSlots(stationDetails.getTotalSlots());
        station.setLatitude(stationDetails.getLatitude());
        station.setLongitude(stationDetails.getLongitude());
        return stationRepository.save(station);
    }

    public List<Station> getFullStations(){
        List<Station> stations = stationRepository.findStationsByStatusIs(EStatus.ACTIVE);
        stations.removeIf(station -> station.getTotalSlots() != bikeRepository.countAllByStation(station));
        return stations;
    }

    public List<Station> getEmptyStations(){
        List<Station> stations = stationRepository.findStationsByStatusIs(EStatus.ACTIVE);
        stations.removeIf(station -> bikeRepository.countAllByStation(station) != 0);
        return stations;
    }

    public boolean coordinatesValid(String latitude, String longitude) {
        double latitudeDouble = Double.parseDouble(latitude);
        double longitudeDouble = Double.parseDouble(longitude);
        if(Math.abs(latitudeDouble) > 90 || Math.abs(longitudeDouble) > 180) {
            return false;
        }
        return true;
    }

    public Object getClosestStation(String postalCode) {

        String url = "http://www.mapquestapi.com/geocoding/v1/address?key=" + System.getenv("GEOCODE_KEY") + "&location=" + postalCode;

        RestTemplate restTemplate = new RestTemplate();
        GeocodeResponse value = restTemplate.getForObject(url, GeocodeResponse.class);
        if(value == null){
            throw new ResourceNotFoundException("Error: Postal Code could not be found.");
        }

        double postalCodeLat = value.results.get(0).locations.get(0).latLng.lat;
        double postalCodeLng = value.results.get(0).locations.get(0).latLng.lng;
        List<Station> stations = stationRepository.findStationsByStatusIsAndIdIsNot(EStatus.ACTIVE, 1L);
        double minDistance = Double.MAX_VALUE;
        Station minStation = null;
        for (Station station: stations) {
            try{
                double stationLat = Double.parseDouble(station.getLatitude());
                double stationLng = Double.parseDouble(station.getLongitude());
                double distance = meters(postalCodeLat, postalCodeLng, stationLat, stationLng);
                if(distance < minDistance){
                    minDistance = distance;
                    minStation = station;
                }
            }catch (NumberFormatException ex){
                throw new IllegalArgumentException("Error: Internal error. Station latitude and longitude is not valid.");
            }
        }
        return minStation;
    }
    private static final double r2d = 180.0D / 3.141592653589793D;
    private static final double d2r = 3.141592653589793D / 180.0D;
    private static final double d2km = 111189.57696D * r2d;
    public static double meters(double lt1, double ln1, double lt2, double ln2) {
        double x = lt1 * d2r;
        double y = lt2 * d2r;
        return Math.acos( Math.sin(x) * Math.sin(y) + Math.cos(x) * Math.cos(y) * Math.cos(d2r * (ln1 - ln2))) * d2km;
    }
}
