<?php

declare(strict_types=1);

namespace App\Framework\Waf\ValueObjects;

use App\Framework\Core\ValueObjects\Coordinates;
use App\Framework\Core\ValueObjects\CountryCode;
use App\Framework\Core\ValueObjects\Timezone;

/**
 * Geographic context information using value objects
 */
final readonly class GeographicContext
{
    public function __construct(
        public ?string $country = null,
        public ?CountryCode $countryCode = null,
        public ?string $region = null,
        public ?string $city = null,
        public ?Coordinates $coordinates = null,
        public ?Timezone $timezone = null,
        public ?string $isp = null
    ) {
    }

    /**
     * Create from basic location data
     */
    public static function fromLocation(
        string $country,
        string $countryCode,
        ?string $city = null,
        ?float $latitude = null,
        ?float $longitude = null
    ): self {
        $coordinates = ($latitude !== null && $longitude !== null)
            ? Coordinates::fromLatLng($latitude, $longitude)
            : null;

        return new self(
            country: $country,
            countryCode: CountryCode::fromString($countryCode),
            city: $city,
            coordinates: $coordinates
        );
    }

    /**
     * Create from GeoIP data
     */
    public static function fromGeoIp(array $geoData): self
    {
        $coordinates = (isset($geoData['latitude']) && isset($geoData['longitude']))
            ? Coordinates::fromLatLng($geoData['latitude'], $geoData['longitude'])
            : null;

        $timezone = isset($geoData['timezone'])
            ? Timezone::fromString($geoData['timezone'])
            : null;

        $countryCode = isset($geoData['country_code'])
            ? CountryCode::fromString($geoData['country_code'])
            : null;

        return new self(
            country: $geoData['country'] ?? null,
            countryCode: $countryCode,
            region: $geoData['region'] ?? null,
            city: $geoData['city'] ?? null,
            coordinates: $coordinates,
            timezone: $timezone,
            isp: $geoData['isp'] ?? null
        );
    }

    /**
     * Check if location is in Europe
     */
    public function isInEurope(): bool
    {
        return $this->countryCode?->isEuropeanUnion() ?? false ||
               $this->coordinates?->getHemisphere()['east_west'] === 'Eastern' &&
               $this->coordinates?->latitude > 35.0 && $this->coordinates?->latitude < 70.0;
    }

    /**
     * Check if location is high-risk for security
     */
    public function isHighRiskLocation(): bool
    {
        return $this->countryCode?->isHighRiskCountry() ?? false;
    }

    /**
     * Get distance to another location in kilometers
     */
    public function distanceTo(self $other): ?float
    {
        if ($this->coordinates === null || $other->coordinates === null) {
            return null;
        }

        return $this->coordinates->distanceTo($other->coordinates);
    }

    /**
     * Check if location is within radius of another location
     */
    public function isWithinRadius(self $center, float $radiusKm): bool
    {
        $distance = $this->distanceTo($center);

        return $distance !== null && $distance <= $radiusKm;
    }

    /**
     * Get continent name
     */
    public function getContinent(): ?string
    {
        return $this->countryCode?->getContinent();
    }

    /**
     * Get timezone offset
     */
    public function getTimezoneOffset(): ?string
    {
        return $this->timezone?->getOffsetString();
    }

    /**
     * Check if coordinates are available
     */
    public function hasCoordinates(): bool
    {
        return $this->coordinates !== null;
    }

    /**
     * Check if timezone information is available
     */
    public function hasTimezone(): bool
    {
        return $this->timezone !== null;
    }

    /**
     * Get location summary
     */
    public function getLocationSummary(): string
    {
        $parts = array_filter([
            $this->city,
            $this->region,
            $this->country ?? $this->countryCode?->getCountryName(),
        ]);

        return implode(', ', $parts);
    }

    public function toArray(): array
    {
        return array_filter([
            'country' => $this->country,
            'country_code' => $this->countryCode?->value,
            'region' => $this->region,
            'city' => $this->city,
            'coordinates' => $this->coordinates?->toArray(),
            'latitude' => $this->coordinates?->latitude,
            'longitude' => $this->coordinates?->longitude,
            'timezone' => $this->timezone?->value,
            'timezone_offset' => $this->timezone?->getOffsetString(),
            'continent' => $this->getContinent(),
            'isp' => $this->isp,
            'is_high_risk' => $this->isHighRiskLocation(),
            'is_in_europe' => $this->isInEurope(),
            'location_summary' => $this->getLocationSummary(),
        ], fn ($value) => $value !== null);
    }
}
