<?php

declare(strict_types=1);

namespace App\Services;

use Database;

use PDO;
use PDOException;
use InvalidArgumentException;
use RuntimeException;

/**
 * Service de gestion des adresses
 *
 * Ce service interroge la base de données externe 'adresses' pour récupérer
 * les adresses géographiques dans des secteurs définis.
 */
class AddressService
{
    private ?PDO $addressesDb = null;
    private PDO $mainDb;
    private $buildingService;

    public function __construct()
    {
        try {
            $this->addressesDb = \AddressesDatabase::getInstance();
            LogService::info('[AddressService] Connexion à la base d\'adresses réussie');
        } catch (\Exception $e) {
            // Si la connexion échoue, on continue sans la base d'adresses
            LogService::error('[AddressService] Connexion à la base d\'adresses impossible', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            $this->addressesDb = null;
        }

        $this->mainDb = \Database::getInstance();
        $this->buildingService = new BuildingService();
    }

    /**
     * Vérifie si la connexion à la base d'adresses est active
     *
     * @return bool
     */
    public function isConnected(): bool
    {
        return $this->addressesDb !== null;
    }

    /**
     * Détermine le département de l'entité courante
     *
     * @param int|null $entityId ID de l'entité
     * @return string|null Code département (ex: "22", "23")
     */
    private function getDepartmentForEntity(?int $entityId = null): ?string
    {
        if (!$entityId) {
            $entityId = $_SESSION['entity_id'] ?? null;
        }

        if (!$entityId) {
            return null;
        }

        try {
            $query = "SELECT departement FROM entites WHERE id = :entity_id";
            $stmt = $this->mainDb->prepare($query);
            $stmt->execute(['entity_id' => $entityId]);
            $result = $stmt->fetch();

            return $result ? $result['departement'] : null;
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Récupère toutes les adresses contenues dans un polygone défini par des coordonnées
     * Gère automatiquement les secteurs multi-départements
     *
     * @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...]
     * @param int|null $entityId ID de l'entité (pour déterminer le département principal)
     * @return array Array des adresses trouvées
     */
    public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array
    {
        // Si pas de connexion à la base d'adresses, retourner un tableau vide
        if (!$this->addressesDb) {
            LogService::error('[AddressService] Pas de connexion à la base d\'adresses externe', [
                'entity_id' => $entityId
            ]);
            return [];
        }

        LogService::info('[AddressService] Début recherche adresses', [
            'entity_id' => $entityId,
            'nb_coordinates' => count($coordinates)
        ]);

        if (count($coordinates) < 3) {
            throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points");
        }

        // D'abord, déterminer tous les départements touchés par ce secteur
        $boundaryService = new DepartmentBoundaryService();
        $departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates);

        if (empty($departmentsTouched)) {
            // Si aucun département n'est trouvé par analyse spatiale,
            // chercher d'abord dans le département de l'entité et ses limitrophes
            $entityDept = $this->getDepartmentForEntity($entityId);
            LogService::info('[AddressService] Département de l\'entité', [
                'departement' => $entityDept
            ]);
            if (!$entityDept) {
                LogService::error('[AddressService] Impossible de déterminer le département de l\'entité', [
                    'entity_id' => $entityId
                ]);
                throw new RuntimeException("Impossible de déterminer le département");
            }

            // Obtenir les départements prioritaires (entité + limitrophes)
            $priorityDepts = $boundaryService->getPriorityDepartments($entityDept);

            // Log pour debug
            LogService::warning('[AddressService] Aucun département trouvé par analyse spatiale', [
                'departements_prioritaires' => implode(', ', $priorityDepts)
            ]);

            // Utiliser les départements prioritaires pour la recherche
            $departmentsTouched = [];
            foreach ($priorityDepts as $deptCode) {
                $departmentsTouched[] = ['code_dept' => $deptCode];
            }
        }

        // Créer le polygone SQL à partir des coordonnées
        $polygonPoints = [];
        foreach ($coordinates as $coord) {
            if (!isset($coord[0]) || !isset($coord[1])) {
                throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude");
            }
            $polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude
        }

        // Fermer le polygone
        $polygonPoints[] = $polygonPoints[0];

        $polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';

        // Collecter les adresses de tous les départements touchés
        $allAddresses = [];

        foreach ($departmentsTouched as $dept) {
            $deptCode = $dept['code_dept'];
            $tableName = "cp" . $deptCode;

            try {
                // Requête pour récupérer les adresses dans le polygone pour ce département
                $sql = "SELECT
                            id,
                            numero,
                            rue as voie,
                            cp as code_postal,
                            ville as commune,
                            gps_lat as latitude,
                            gps_lng as longitude,
                            x,
                            y,
                            code_insee,
                            nom_ld,
                            ville_acheminement,
                            rue_afnor,
                            source,
                            certification,
                            :dept_code as departement
                        FROM `$tableName`
                        WHERE ST_Contains(
                            ST_GeomFromText(:polygon, 4326),
                            POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8)))
                        )
                        AND gps_lat != ''
                        AND gps_lng != ''";

                $stmt = $this->addressesDb->prepare($sql);
                $stmt->execute([
                    'polygon' => $polygonString,
                    'dept_code' => $deptCode
                ]);

                $addresses = $stmt->fetchAll();

                // Ajouter les adresses à la collection globale
                foreach ($addresses as $address) {
                    $allAddresses[] = $address;
                }

                // Log pour debug
                LogService::info('[AddressService] Recherche dans table', [
                    'table' => $tableName,
                    'departement' => $deptCode,
                    'nb_adresses' => count($addresses)
                ]);

            } catch (PDOException $e) {
                // Log l'erreur mais continue avec les autres départements
                LogService::error('[AddressService] Erreur SQL', [
                    'table' => $tableName,
                    'departement' => $deptCode,
                    'error' => $e->getMessage(),
                    'code' => $e->getCode()
                ]);
            }
        }

        LogService::info('[AddressService] Fin recherche adresses', [
            'total_adresses' => count($allAddresses)
        ]);
        return $allAddresses;
    }

    /**
     * Enrichit les adresses avec les données bâtiments depuis la base 'batiments'
     *
     * Pour chaque adresse trouvée, cette méthode cherche si un bâtiment existe
     * et ajoute les métadonnées (nb_log, residence, fk_habitat, etc.)
     *
     * @param array $addresses Liste d'adresses depuis getAddressesInPolygon()
     * @param int|null $entityId ID de l'entité (pour logs)
     * @return array Adresses enrichies avec données bâtiment
     */
    public function enrichAddressesWithBuildings(array $addresses, ?int $entityId = null): array
    {
        if (empty($addresses)) {
            return [];
        }

        LogService::info('[AddressService] Début enrichissement avec bâtiments', [
            'entity_id' => $entityId,
            'nb_addresses' => count($addresses)
        ]);

        try {
            $enrichedAddresses = $this->buildingService->enrichAddresses($addresses);

            // Compter les immeubles vs maisons
            $nbImmeubles = 0;
            $nbMaisons = 0;
            foreach ($enrichedAddresses as $addr) {
                if (isset($addr['fk_habitat']) && $addr['fk_habitat'] == 2) {
                    $nbImmeubles++;
                } else {
                    $nbMaisons++;
                }
            }

            LogService::info('[AddressService] Fin enrichissement avec bâtiments', [
                'total_adresses' => count($enrichedAddresses),
                'nb_immeubles' => $nbImmeubles,
                'nb_maisons' => $nbMaisons
            ]);

            return $enrichedAddresses;

        } catch (\Exception $e) {
            LogService::error('[AddressService] Erreur lors de l\'enrichissement', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            // En cas d'erreur, retourner les adresses sans enrichissement
            return $addresses;
        }
    }

    /**
     * Récupère les adresses dans un rayon autour d'un point
     *
     * @param float $latitude Latitude du centre
     * @param float $longitude Longitude du centre
     * @param float $radiusMeters Rayon en mètres
     * @param int|null $entityId ID de l'entité (pour déterminer le département)
     * @return array Array des adresses trouvées
     */
    public function getAddressesInRadius(float $latitude, float $longitude, float $radiusMeters, ?int $entityId = null): array
    {
        // Déterminer le département
        $dept = $this->getDepartmentForEntity($entityId);
        if (!$dept) {
            throw new RuntimeException("Impossible de déterminer le département de l'entité");
        }

        // Nom de la table selon le département
        $tableName = "cp" . $dept;

        try {
            // Utiliser ST_Distance_Sphere pour calculer la distance en mètres
            $sql = "SELECT
                        id,
                        numero,
                        rue as voie,
                        cp as code_postal,
                        ville as commune,
                        gps_lat as latitude,
                        gps_lng as longitude,
                        ST_Distance_Sphere(
                            POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
                            ST_GeomFromText(:point, 4326)
                        ) as distance
                    FROM `$tableName`
                    WHERE ST_Distance_Sphere(
                        POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
                        ST_GeomFromText(:point, 4326)
                    ) <= :radius
                    AND gps_lat != ''
                    AND gps_lng != ''
                    ORDER BY distance";

            $point = "POINT($longitude $latitude)";

            $stmt = $this->addressesDb->prepare($sql);
            $stmt->execute([
                'point' => $point,
                'radius' => $radiusMeters
            ]);

            return $stmt->fetchAll();
        } catch (PDOException $e) {
            throw new RuntimeException("Erreur lors de la récupération des adresses dans la table $tableName : " . $e->getMessage());
        }
    }

    /**
     * Compte le nombre d'adresses dans un polygone
     * Gère automatiquement les secteurs multi-départements
     *
     * @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...]
     * @param int|null $entityId ID de l'entité (pour déterminer le département principal)
     * @return int Nombre d'adresses
     */
    public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int
    {
        // Si pas de connexion à la base d'adresses, retourner 0
        if (!$this->addressesDb) {
            return 0;
        }

        if (count($coordinates) < 3) {
            throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points");
        }

        // D'abord, déterminer tous les départements touchés par ce secteur
        $boundaryService = new DepartmentBoundaryService();
        $departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates);

        if (empty($departmentsTouched)) {
            // Si aucun département n'est trouvé, utiliser le département de l'entité
            $dept = $this->getDepartmentForEntity($entityId);
            if (!$dept) {
                throw new RuntimeException("Impossible de déterminer le département");
            }
            $departmentsTouched = [['code_dept' => $dept]];
        }

        // Créer le polygone SQL à partir des coordonnées
        $polygonPoints = [];
        foreach ($coordinates as $coord) {
            if (!isset($coord[0]) || !isset($coord[1])) {
                throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude");
            }
            $polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude
        }

        // Fermer le polygone
        $polygonPoints[] = $polygonPoints[0];

        $polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';

        // Compter les adresses dans tous les départements touchés
        $totalCount = 0;

        foreach ($departmentsTouched as $dept) {
            $deptCode = $dept['code_dept'];
            $tableName = "cp" . $deptCode;

            try {
                $sql = "SELECT COUNT(*) as count
                        FROM `$tableName`
                        WHERE ST_Contains(
                            ST_GeomFromText(:polygon, 4326),
                            POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8)))
                        )
                        AND gps_lat != ''
                        AND gps_lng != ''";

                $stmt = $this->addressesDb->prepare($sql);
                $stmt->execute(['polygon' => $polygonString]);

                $result = $stmt->fetch();
                $deptCount = (int)$result['count'];
                $totalCount += $deptCount;

            } catch (PDOException $e) {
                // Log l'erreur mais continue avec les autres départements
                error_log("Erreur de comptage pour le département $deptCode : " . $e->getMessage());
            }
        }

        return $totalCount;
    }
}
