<?php
declare(strict_types=1);

namespace App\Services;

use App\Entity\Galaxy;
use App\Entity\Planet;
use App\Entity\Sector;
use App\Entity\System;
use App\Enum\CelestialBodySizeEnum;
use App\Enum\PlanetHabitabilityEnum;
use App\Enum\PlanetTypeEnum;
use App\Enum\StarTypeEnum;

class GalaxyFactory
{
    const SYSTEMS_PER_SECTOR = 12;

    public function createGalaxy(int $nbSectors = 1000, string $name = 'Galaxy'): Galaxy
    {
        $galaxy = new Galaxy();
        $galaxy->setName($name);

        for ($i = 0; $i < $nbSectors; $i++) {
            $sector = $this->createSector($i + 1);
            $galaxy->addSector($sector);
        }

        return $galaxy;
    }

    protected function createSector(int $number): Sector {
        $sector = new Sector();
        $sector->setNumber($number);
        $sector->setName("Sector $number");

        for ($i = 0; $i < self::SYSTEMS_PER_SECTOR; $i++) {
            $system = $this->createSystem();
            $sector->addSystem($system);
        }

        return $sector;
    }

    protected function createSystem(): System {
        $system = new System();

        // 1. Choisir aléatoirement un type d'étoile avec des proportions réalistes
        $starType = $this->generateRandomStarType();
        $system->setStarType($starType);

        // Générer une taille d'étoile appropriée
        $starSize = $this->generateStarSize($starType);
        $system->setStarSize($starSize);

        // Générer un nom de système (temporaire)
        $system->setName($this->generateSystemName());

        // 2. Selon le type d'étoile, déterminer s'il y a des planètes
        if ($this->shouldHavePlanets($starType)) {
            // 3. Ajouter des planètes de manière réaliste
            $this->generatePlanets($system, $starType);

            // 5. Ajouter une ceinture d'astéroïdes si approprié
            if ($this->shouldHaveAsteroidBelt($system->getPlanets()->count())) {
                $system->setAsteroidBeltPosition($this->generateAsteroidBeltPosition($system->getPlanets()->count()));
            }
        }

        return $system;
    }

    private function generateRandomStarType(): StarTypeEnum
    {
        $rand = mt_rand(1, 1000);

        // Proportions approximatives observées dans la galaxie
        if ($rand <= 750) return StarTypeEnum::RED_DWARF;      // ~75%
        if ($rand <= 870) return StarTypeEnum::ORANGE_DWARF;   // ~12%
        if ($rand <= 940) return StarTypeEnum::YELLOW_DWARF;   // ~7%
        if ($rand <= 970) return StarTypeEnum::F_STAR;        // ~3%
        if ($rand <= 990) return StarTypeEnum::A_STAR;        // ~2%
        if ($rand <= 998) return StarTypeEnum::SUB_GIANT;     // ~0.8%
        return StarTypeEnum::T_STAR;                          // ~0.2%
    }

    private function generateStarSize(StarTypeEnum $starType): CelestialBodySizeEnum
    {
        // Distribution réaliste des tailles selon le type d'étoile
        $rand = mt_rand(1, 100);

        return match($starType) {
            StarTypeEnum::RED_DWARF => $rand <= 80 ? CelestialBodySizeEnum::S : CelestialBodySizeEnum::M,
            StarTypeEnum::ORANGE_DWARF => $rand <= 60 ? CelestialBodySizeEnum::M : CelestialBodySizeEnum::L,
            StarTypeEnum::YELLOW_DWARF => $rand <= 70 ? CelestialBodySizeEnum::M : CelestialBodySizeEnum::L,
            StarTypeEnum::F_STAR => $rand <= 40 ? CelestialBodySizeEnum::M : CelestialBodySizeEnum::L,
            StarTypeEnum::A_STAR => $rand <= 20 ? CelestialBodySizeEnum::L : CelestialBodySizeEnum::XL,
            StarTypeEnum::SUB_GIANT => $rand <= 30 ? CelestialBodySizeEnum::L : CelestialBodySizeEnum::XL,
            StarTypeEnum::T_STAR => $rand <= 10 ? CelestialBodySizeEnum::XL : CelestialBodySizeEnum::XXL,
        };
    }

    private function shouldHavePlanets(StarTypeEnum $starType): bool
    {
        $rand = mt_rand(1, 100);

        // Probabilité d'avoir des planètes selon le type d'étoile
        return match($starType) {
            StarTypeEnum::RED_DWARF => $rand <= 85,    // 85% de chance
            StarTypeEnum::ORANGE_DWARF => $rand <= 90, // 90% de chance
            StarTypeEnum::YELLOW_DWARF => $rand <= 95, // 95% de chance
            StarTypeEnum::F_STAR => $rand <= 80,       // 80% de chance
            StarTypeEnum::A_STAR => $rand <= 60,       // 60% de chance
            StarTypeEnum::SUB_GIANT => $rand <= 40,    // 40% de chance
            StarTypeEnum::T_STAR => $rand <= 20,       // 20% de chance
        };
    }

    private function generatePlanets(System $system, StarTypeEnum $starType): void
    {
        $maxPlanets = $this->getMaxPlanets($starType);
        $numPlanets = mt_rand(1, $maxPlanets);

        for ($position = 1; $position <= $numPlanets; $position++) {
            $planet = $this->createPlanet($position, $starType, $numPlanets);
            $system->addPlanet($planet);
        }

        // 4. Rendre certaines planètes habitables/terraformables
        $this->assignHabitability($system, $starType);
    }

    private function getMaxPlanets(StarTypeEnum $starType): int
    {
        return match($starType) {
            StarTypeEnum::RED_DWARF => mt_rand(1, 4),      // 1-4 planètes
            StarTypeEnum::ORANGE_DWARF => mt_rand(2, 6),   // 2-6 planètes
            StarTypeEnum::YELLOW_DWARF => mt_rand(3, 8),   // 3-8 planètes
            StarTypeEnum::F_STAR => mt_rand(2, 7),         // 2-7 planètes
            StarTypeEnum::A_STAR => mt_rand(1, 5),         // 1-5 planètes
            StarTypeEnum::SUB_GIANT => mt_rand(1, 3),      // 1-3 planètes
            StarTypeEnum::T_STAR => mt_rand(1, 2),         // 1-2 planètes
        };
    }

    private function createPlanet(int $position, StarTypeEnum $starType, int $totalPlanets): Planet
    {
        $planet = new Planet();
        $planet->setPosition($position);

        // Déterminer le type de planète selon la position
        $planetType = $this->getPlanetType($position, $totalPlanets, $starType);
        $planet->setType($planetType);

        // Déterminer la taille de la planète
        $planetSize = $this->getPlanetSize($planetType);
        $planet->setSize($planetSize);

        // Nom temporaire
        $planet->setName("Planet $position");

        // Habitabilité par défaut (sera modifiée par assignHabitability)
        $planet->setHabitability(PlanetHabitabilityEnum::UNINHABITABLE);

        return $planet;
    }

    private function getPlanetType(int $position, int $totalPlanets, StarTypeEnum $starType): PlanetTypeEnum
    {
        $innerZone = ceil($totalPlanets * 0.4);  // 40% des positions sont la zone interne
        $middleZone = ceil($totalPlanets * 0.7); // 70% des positions sont zone interne + moyenne

        if ($position <= $innerZone) {
            // Zone interne : principalement des planètes telluriques
            return mt_rand(1, 100) <= 85 ? PlanetTypeEnum::TERRESTRIAL : PlanetTypeEnum::DWARF;
        } elseif ($position <= $middleZone) {
            // Zone moyenne : mélange de types
            $rand = mt_rand(1, 100);
            if ($rand <= 40) return PlanetTypeEnum::TERRESTRIAL;
            if ($rand <= 70) return PlanetTypeEnum::GAS;
            return PlanetTypeEnum::ICE;
        } else {
            // Zone externe : principalement des géantes gazeuses et planètes glacées/naines
            $rand = mt_rand(1, 100);
            if ($rand <= 45) return PlanetTypeEnum::GAS;
            if ($rand <= 75) return PlanetTypeEnum::ICE;
            return PlanetTypeEnum::DWARF;
        }
    }

    private function getPlanetSize(PlanetTypeEnum $type): CelestialBodySizeEnum
    {
        $rand = mt_rand(1, 100);

        return match($type) {
            PlanetTypeEnum::TERRESTRIAL => match(true) {
                $rand <= 30 => CelestialBodySizeEnum::S,
                $rand <= 70 => CelestialBodySizeEnum::M,
                default => CelestialBodySizeEnum::L
            },
            PlanetTypeEnum::GAS => match(true) {
                $rand <= 20 => CelestialBodySizeEnum::L,
                $rand <= 60 => CelestialBodySizeEnum::XL,
                default => CelestialBodySizeEnum::XXL
            },
            PlanetTypeEnum::ICE => match(true) {
                $rand <= 50 => CelestialBodySizeEnum::S,
                $rand <= 80 => CelestialBodySizeEnum::M,
                default => CelestialBodySizeEnum::L
            },
            PlanetTypeEnum::DWARF => match(true) {
                $rand <= 70 => CelestialBodySizeEnum::XS,
                default => CelestialBodySizeEnum::S
            },
        };
    }

    private function assignHabitability(System $system, StarTypeEnum $starType): void
    {
        $planets = $system->getPlanets()->toArray();
        $totalPlanets = count($planets);

        if ($totalPlanets === 0) return;

        // Déterminer la zone habitable selon le type d'étoile
        $habitableZone = $this->getHabitableZone($starType, $totalPlanets);

        foreach ($planets as $planet) {
            if ($planet->getType() === PlanetTypeEnum::TERRESTRIAL) {
                $habitability = $this->calculateHabitability($planet->getPosition(), $habitableZone, $starType);
                $planet->setHabitability($habitability);
            }
        }
    }

    private function getHabitableZone(StarTypeEnum $starType, int $totalPlanets): array
    {
        // Retourne [min, max] des positions dans la zone habitable
        return match($starType) {
            StarTypeEnum::RED_DWARF => [1, min(2, $totalPlanets)],      // Zone très proche
            StarTypeEnum::ORANGE_DWARF => [2, min(4, $totalPlanets)],   // Zone proche-moyenne
            StarTypeEnum::YELLOW_DWARF => [2, min(5, $totalPlanets)],   // Zone étendue
            StarTypeEnum::F_STAR => [3, min(6, $totalPlanets)],         // Zone plus lointaine
            StarTypeEnum::A_STAR => [4, min(7, $totalPlanets)],         // Zone très lointaine
            StarTypeEnum::SUB_GIANT => [3, min(5, $totalPlanets)],      // Zone variable
            StarTypeEnum::T_STAR => [5, min(8, $totalPlanets)],         // Zone très éloignée
        };
    }

    private function calculateHabitability(int $position, array $habitableZone, StarTypeEnum $starType): PlanetHabitabilityEnum
    {
        [$minHabitable, $maxHabitable] = $habitableZone;
        $rand = mt_rand(1, 100);

        if ($position >= $minHabitable && $position <= $maxHabitable) {
            // Dans la zone habitable
            $habitabilityChance = match($starType) {
                StarTypeEnum::RED_DWARF => 30,     // 30% de chance d'être habitable
                StarTypeEnum::ORANGE_DWARF => 40,  // 40% de chance
                StarTypeEnum::YELLOW_DWARF => 50,  // 50% de chance
                StarTypeEnum::F_STAR => 35,        // 35% de chance
                StarTypeEnum::A_STAR => 20,        // 20% de chance
                StarTypeEnum::SUB_GIANT => 15,     // 15% de chance
                StarTypeEnum::T_STAR => 10,        // 10% de chance
            };

            if ($rand <= $habitabilityChance) {
                return PlanetHabitabilityEnum::INHABITABLE;
            } elseif ($rand <= $habitabilityChance + 25) {
                return PlanetHabitabilityEnum::TERRAFORMABLE;
            }
        } elseif (abs($position - ($minHabitable + $maxHabitable) / 2) <= 1) {
            // Proche de la zone habitable
            if ($rand <= 15) {
                return PlanetHabitabilityEnum::TERRAFORMABLE;
            }
        }

        return PlanetHabitabilityEnum::UNINHABITABLE;
    }

    private function shouldHaveAsteroidBelt(int $numPlanets): bool
    {
        // Plus il y a de planètes, plus il y a de chances d'avoir une ceinture d'astéroïdes
        $chance = min(30 + ($numPlanets * 10), 70); // Entre 30% et 70% selon le nombre de planètes
        return mt_rand(1, 100) <= $chance;
    }

    private function generateAsteroidBeltPosition(int $numPlanets): int
    {
        // La ceinture d'astéroïdes se trouve généralement entre les planètes internes et externes
        $minPosition = max(2, (int)ceil($numPlanets * 0.3));
        $maxPosition = max($minPosition, min($numPlanets + 1, (int)ceil($numPlanets * 0.7)));

        return mt_rand($minPosition, $maxPosition);
    }

    private function generateSystemName(): string
    {
        // Générateur de noms temporaire - pourrait être amélioré avec des noms plus réalistes
        $prefixes = ['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta'];
        $suffixes = ['Centauri', 'Draconis', 'Aquilae', 'Orionis', 'Virginis', 'Leonis', 'Ursae', 'Cygni'];

        return $prefixes[array_rand($prefixes)] . ' ' . $suffixes[array_rand($suffixes)] . ' ' . mt_rand(1, 999);
    }
}