package ru.soune.nocopy.service.geo;

import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.model.CountryResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
@Slf4j
public class GeoCountryService {

    private final DatabaseReader countryDbReader;
    private final DatabaseReader cityDbReader;

    public GeoCountryService() throws IOException {
        Resource resource = new ClassPathResource("geo/GeoLite2-Country.mmdb");
        this.countryDbReader = new DatabaseReader.Builder(resource.getInputStream()).build();

        Resource cityResource = new ClassPathResource("geo/GeoLite2-City.mmdb");
        this.cityDbReader = new DatabaseReader.Builder(cityResource.getInputStream()).build();
    }

    public String getCountryName(String input) {
        try {
            CountryResponse response = extractCountry(input);

            if (response == null || response.getCountry() == null) {
                return "Not defined";
            }

            String countryName = response.getCountry().getName();
            return countryName != null ? countryName : "Not defined";

        } catch (UnknownHostException e) {
            log.warn("Cannot resolve host for input: {}", input);
            return "Not defined";
        } catch (Exception e) {
            log.error("Failed to get country for input: {}", input, e);
            return "unknown";
        }
    }

    public Map<String, Integer> getUniqueGeoCount(List<String> ipList, String locale) {
        Set<String> uniqueCountries = new HashSet<>();
        Set<String> uniqueCountryCodes = new HashSet<>();
        Set<String> uniqueCities = new HashSet<>();

        if (ipList != null) {
            for (String ip : ipList) {
                try {
                    String country = getCountryByIp(ip, locale);
                    if (!"unknown".equals(country) && !"Not defined".equals(country)) {
                        uniqueCountries.add(country);
                    }

                    String countryCode = getCountryCodeByIp(ip);
                    if (!"unknown".equals(countryCode) && countryCode != null) {
                        uniqueCountryCodes.add(countryCode);
                    }

                    String city = getCityByIp(ip, locale);
                    if (!"unknown".equals(city) && !"Not defined".equals(city)) {
                        uniqueCities.add(city);
                    }
                } catch (Exception e) {
                    log.error("Error processing IP: {}", ip, e);
                }
            }
        }

        return Map.of(
                "uniqueCountries", uniqueCountries.size(),
                "uniqueCountryCodes", uniqueCountryCodes.size(),
                "uniqueCities", uniqueCities.size()
        );
    }

    public String getCountryCode(String input) {
        try {
            CountryResponse response = extractCountry(input);

            if (response == null || response.getCountry() == null) {
                return "Not defined";
            }

            String isoCode = response.getCountry().getIsoCode();
            return isoCode != null ? isoCode : "Not defined";

        } catch (UnknownHostException e) {
            log.warn("Cannot resolve host for input: {}", input);
            return "Not defined";
        } catch (Exception e) {
            log.error("Failed to get country code for input: {}", input, e);
            return "unknown";
        }
    }

    public String getCityByIp(String ip, String locale) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CityResponse response = cityDbReader.city(ipAddress);

            if (response.getCity() == null) {
                return "Not defined";
            }

            String cityName = response.getCity().getNames().getOrDefault(locale,
                    response.getCity().getName());

            return cityName != null ? cityName : "Not defined";

        } catch (UnknownHostException e) {
            log.warn("Cannot resolve IP: {}", ip);
            return "Not defined";
        } catch (Exception e) {
            log.error("Failed to get city for IP: {}", ip, e);
            return "unknown";
        }
    }

    public String getCountryByIp(String ip, String locale) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CountryResponse response = countryDbReader.country(ipAddress);

            if (response.getCountry() == null) {
                return "Not defined";
            }

            String countryName = response.getCountry().getNames().getOrDefault(locale,
                    response.getCountry().getName());

            return countryName != null ? countryName : "Not defined";

        } catch (UnknownHostException e) {
            log.warn("Cannot resolve IP: {}", ip);
            return "Not defined";
        } catch (Exception e) {
            log.error("Failed to get country for IP: {}", ip, e);
            return "unknown";
        }
    }

    public String getCountryCodeByIp(String ip) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CountryResponse response = countryDbReader.country(ipAddress);

            return response.getCountry() != null ? response.getCountry().getIsoCode() : null;

        } catch (UnknownHostException e) {
            log.warn("Cannot resolve IP: {}", ip);
            return null;
        } catch (Exception e) {
            log.error("Failed to get country code for IP: {}", ip, e);
            return "unknown";
        }
    }

    private CountryResponse extractCountry(String input) throws IOException, GeoIp2Exception {
        String domain = extractDomain(input);

        InetAddress ip;
        try {
            ip = InetAddress.getByName(domain);
        } catch (UnknownHostException e) {
            if (!domain.startsWith("www.")) {
                try {
                    ip = InetAddress.getByName("www." + domain);
                } catch (UnknownHostException ex) {
                    throw new UnknownHostException("Cannot resolve domain: " + domain);
                }
            } else {
                throw e;
            }
        }

        return countryDbReader.country(ip);
    }

    private String extractDomain(String input) {
        if (input == null || input.isEmpty()) {
            throw new IllegalArgumentException("Input is empty");
        }

        try {
            String cleaned = input.trim().toLowerCase();

            cleaned = cleaned.replaceAll("^https?://", "");

            cleaned = cleaned.replaceAll("^www\\.", "");

            cleaned = cleaned.split("/")[0];

            cleaned = cleaned.split(":")[0];

            if (cleaned.contains("?")) {
                cleaned = cleaned.split("\\?")[0];
            }

            cleaned = cleaned.trim();

            if (!cleaned.contains(".") || cleaned.length() < 4) {
                throw new IllegalArgumentException("Invalid domain format: " + cleaned);
            }

            return cleaned;

        } catch (Exception e) {
            log.error("Failed to extract domain from: {}", input, e);
            throw new IllegalArgumentException("Cannot extract domain from: " + input, e);
        }
    }
}