package com.honey.honey.service;

import com.maxmind.db.CHMCache;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CountryResponse;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;

/**
 * Service for determining country code from IP address using MaxMind GeoLite2 database.
 * Thread-safe singleton that maintains a single DatabaseReader instance.
 */
@Slf4j
@Service
public class CountryCodeService {

    private final String dbPath;
    private final ResourceLoader resourceLoader;
    private DatabaseReader reader;

    public CountryCodeService(
            @Value("${geoip.db-path:}") String dbPath,
            ResourceLoader resourceLoader) {
        this.dbPath = dbPath;
        this.resourceLoader = resourceLoader;
    }

    @PostConstruct
    public void init() throws IOException {
        File dbFile = null;

        // Try external file path first (from config/env)
        if (dbPath != null && !dbPath.isBlank()) {
            dbFile = new File(dbPath);
            if (dbFile.exists() && dbFile.isFile()) {
                log.info("Loading GeoIP database from external path: {}", dbFile.getAbsolutePath());
            } else {
                log.warn("GeoIP database file not found at configured path: {}, falling back to resources", dbFile.getAbsolutePath());
                dbFile = null;
            }
        }

        // Fallback to resources directory
        if (dbFile == null) {
            try {
                Resource resource = resourceLoader.getResource("classpath:geoip/GeoLite2-Country.mmdb");
                if (resource.exists() && resource.isReadable()) {
                    // Try to get file path first (works if resource is a file system resource)
                    try {
                        dbFile = resource.getFile();
                        log.info("Loading GeoIP database from resources: {}", dbFile.getAbsolutePath());
                    } catch (Exception e) {
                        // Resource is in JAR, need to extract to temp file
                        log.debug("GeoIP database is in JAR, extracting to temp file");
                        InputStream inputStream = resource.getInputStream();
                        File tempFile = File.createTempFile("GeoLite2-Country", ".mmdb");
                        tempFile.deleteOnExit();
                        
                        try (inputStream) {
                            java.nio.file.Files.copy(inputStream, tempFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
                        }
                        
                        dbFile = tempFile;
                        log.info("Loading GeoIP database from JAR resources (extracted to temp file)");
                    }
                } else {
                    throw new IllegalStateException("GeoIP database not found in resources: classpath:geoip/GeoLite2-Country.mmdb");
                }
            } catch (Exception e) {
                throw new IllegalStateException("Failed to load GeoIP database from resources: " + e.getMessage(), e);
            }
        }

        // Initialize DatabaseReader with cache for better performance
        try {
            this.reader = new DatabaseReader.Builder(dbFile)
                    .withCache(new CHMCache())
                    .build();
            log.info("GeoIP database loaded successfully");
        } catch (IOException e) {
            log.error("Failed to initialize GeoIP database reader", e);
            throw new IllegalStateException("Failed to initialize GeoIP database reader", e);
        }
    }

    @PreDestroy
    public void destroy() {
        if (reader != null) {
            try {
                reader.close();
                log.info("GeoIP database reader closed");
            } catch (IOException e) {
                log.warn("Error closing GeoIP database reader", e);
            }
        }
    }

    /**
     * Determines country code from IP address using MaxMind GeoLite2 database.
     * 
     * @param ipAddress IP address as string (IPv4 or IPv6)
     * @return ISO 3166-1 alpha-2 country code (e.g., "UA", "PL", "DE"), or "XX" if unknown/invalid/private
     */
    public String getCountryCode(String ipAddress) {
        if (ipAddress == null || ipAddress.isBlank()) {
            return "XX";
        }

        if (reader == null) {
            log.warn("GeoIP database reader not initialized, returning 'XX' for IP: {}", ipAddress);
            return "XX";
        }

        try {
            InetAddress addr = InetAddress.getByName(ipAddress);
            
            // Check if it's a private/local IP (won't be in GeoIP database)
            if (addr.isLoopbackAddress() || addr.isLinkLocalAddress() || addr.isSiteLocalAddress()) {
                log.debug("Private/local IP address detected: {}, returning 'XX'", ipAddress);
                return "XX";
            }

            CountryResponse response = reader.country(addr);
            String iso = response.getCountry().getIsoCode();
            
            if (iso != null && iso.length() == 2) {
                return iso.toUpperCase(); // Ensure uppercase (ISO codes should be uppercase)
            } else {
                log.debug("Invalid ISO code returned for IP {}: {}", ipAddress, iso);
                return "XX";
            }
        } catch (IOException e) {
            log.debug("Failed to resolve IP address: {}", ipAddress, e);
            return "XX";
        } catch (GeoIp2Exception e) {
            log.debug("GeoIP lookup failed for IP {}: {}", ipAddress, e.getMessage());
            return "XX";
        } catch (IllegalArgumentException e) {
            log.debug("Invalid IP address format: {}", ipAddress, e);
            return "XX";
        }
    }
    
}

