<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

/**
 * Complete address combining street, city, postal code and country
 * Immutable value object for address handling
 */
final readonly class Address
{
    public function __construct(
        public Street $street,
        public City $city,
        public PostalCode $postalCode,
        public CountryCode $country
    ) {
    }

    public static function create(
        Street $street,
        City $city,
        PostalCode $postalCode,
        CountryCode $country
    ): self {
        return new self($street, $city, $postalCode, $country);
    }

    public static function fromParts(
        string $streetName,
        string $streetNumber,
        string $cityName,
        string $postalCode,
        string $countryCode,
        ?string $streetAddition = null,
        ?string $cityState = null
    ): self {
        $country = CountryCode::fromString($countryCode);

        return new self(
            new Street($streetName, $streetNumber, $streetAddition),
            new City($cityName, $cityState, $country),
            new PostalCode($postalCode, $country),
            $country
        );
    }

    public function withStreet(Street $street): self
    {
        return new self($street, $this->city, $this->postalCode, $this->country);
    }

    public function withCity(City $city): self
    {
        return new self($this->street, $city, $this->postalCode, $this->country);
    }

    public function withPostalCode(PostalCode $postalCode): self
    {
        return new self($this->street, $this->city, $postalCode, $this->country);
    }

    public function withCountry(CountryCode $country): self
    {
        // Update postal code and city country references
        $newPostalCode = new PostalCode($this->postalCode->code, $country);
        $newCity = $this->city->withCountry($country);

        return new self($this->street, $newCity, $newPostalCode, $country);
    }

    public function format(string $style = 'multiline'): string
    {
        return match($style) {
            'single_line' => $this->formatSingleLine(),
            'multiline' => $this->formatMultiline(),
            'postal' => $this->formatPostal(),
            default => $this->formatMultiline()
        };
    }

    private function formatSingleLine(): string
    {
        return sprintf(
            '%s, %s %s, %s',
            $this->street->format(),
            $this->postalCode->format(),
            $this->city->name,
            $this->country->getCountryName() ?? $this->country->value
        );
    }

    private function formatMultiline(): string
    {
        $lines = [
            $this->street->format(),
            $this->postalCode->format() . ' ' . $this->city->format(false),
        ];

        if ($this->city->hasState()) {
            $lines[1] = $this->postalCode->format() . ' ' . $this->city->name . ', ' . $this->city->state;
        }

        $lines[] = $this->country->getCountryName() ?? $this->country->value;

        return implode("\n", $lines);
    }

    private function formatPostal(): string
    {
        // Format suitable for postal services
        return match($this->country->value) {
            'US' => $this->formatUSPostal(),
            'GB' => $this->formatUKPostal(),
            'DE' => $this->formatGermanPostal(),
            default => $this->formatMultiline()
        };
    }

    private function formatUSPostal(): string
    {
        $state = $this->city->state ?? '';

        return sprintf(
            "%s\n%s, %s %s\n%s",
            $this->street->format(),
            $this->city->name,
            $state,
            $this->postalCode->format(),
            'USA'
        );
    }

    private function formatUKPostal(): string
    {
        return sprintf(
            "%s\n%s\n%s\n%s",
            $this->street->format(),
            $this->city->name,
            $this->postalCode->format(),
            'United Kingdom'
        );
    }

    private function formatGermanPostal(): string
    {
        return sprintf(
            "%s\n%s %s\n%s",
            $this->street->format('number_first'),
            $this->postalCode->format(),
            $this->city->name,
            'Deutschland'
        );
    }

    public function getRegion(): ?string
    {
        return $this->city->state;
    }

    public function isInCountry(CountryCode $country): bool
    {
        return $this->country->value === $country->value;
    }

    public function isInSameCity(self $other): bool
    {
        return $this->city->equals($other->city)
            && $this->country->value === $other->country->value;
    }

    public function isInSamePostalCode(self $other): bool
    {
        return $this->postalCode->equals($other->postalCode);
    }

    public function equals(self $other): bool
    {
        return $this->street->equals($other->street)
            && $this->city->equals($other->city)
            && $this->postalCode->equals($other->postalCode)
            && $this->country->value === $other->country->value;
    }

    public function toString(): string
    {
        return $this->format('single_line');
    }

    public function __toString(): string
    {
        return $this->format('single_line');
    }

    public function toArray(): array
    {
        return [
            'street' => $this->street->toArray(),
            'city' => $this->city->toArray(),
            'postal_code' => $this->postalCode->toArray(),
            'country' => $this->country->value,
            'formatted' => [
                'single_line' => $this->format('single_line'),
                'multiline' => $this->format('multiline'),
                'postal' => $this->format('postal'),
            ],
        ];
    }

    public static function fromArray(array $data): self
    {
        return new self(
            Street::fromArray($data['street']),
            City::fromArray($data['city']),
            PostalCode::fromArray($data['postal_code']),
            CountryCode::fromString($data['country'])
        );
    }
}
