from __future__ import annotations

import hashlib

import pytest

from osm_lead_source_service.domain.enums import OsmObjectType
from osm_lead_source_service.domain.models import OsmIdentity, RegionScopedIdentity
from osm_lead_source_service.errors import ContractViolation
from osm_lead_source_service.normalization import (
    NormalizationContext,
    RawBusinessFields,
    normalize_address,
    normalize_business,
    normalize_domain,
    normalize_name,
    normalize_phone,
    raw_business_fields_from_parsed_object,
)
from osm_lead_source_service.pbf import ParsedOsmObject

_SOURCE_HASH = hashlib.sha256(b"fixture").hexdigest()


def test_name_and_address_normalization_are_unicode_stable() -> None:
    assert normalize_name("  Café & Ｃｏ.  ") == "café and co"
    assert normalize_address("12/4, Main-St. — North") == "12 4 main st north"


def test_domain_normalization_is_idna_and_host_only() -> None:
    assert normalize_domain("https://www.BÜCHER.de/path?q=1") == "xn--bcher-kva.de"
    assert normalize_domain("user:pass@example.com") is None
    assert normalize_domain("localhost:8000") is None
    assert normalize_domain("single-label") is None
    assert normalize_domain("192.0.2.10") is None
    assert normalize_domain("[2001:db8::1]") is None


def test_phone_normalization_uses_explicit_country_context() -> None:
    au = NormalizationContext(country_code="AU", calling_code="61")
    th = NormalizationContext(country_code="TH", calling_code="66")
    assert normalize_phone("(02) 1234 5678 ext. 9", au) == "+61212345678"
    assert normalize_phone("081-234-5678", th) == "+66812345678"
    assert normalize_phone("0044 20 7123 4567", au) == "+442071234567"
    assert normalize_phone("61212345678", au) == "+61212345678"
    assert normalize_phone("call 02 1234 5678", au) is None


def test_text_normalization_is_bounded() -> None:
    with pytest.raises(ContractViolation, match="bounded length"):
        normalize_name("x" * 4097)
    with pytest.raises(ContractViolation, match="normalized text"):
        normalize_name("x" * 1025)


def test_parsed_object_field_precedence_and_coordinates() -> None:
    parsed = ParsedOsmObject(
        identity=OsmIdentity(OsmObjectType.NODE, 1),
        version=1,
        tags=(
            ("addr:city", "Chiang Mai"),
            ("addr:country", "th"),
            ("addr:housenumber", "12"),
            ("addr:street", "Nimman Road"),
            ("brand", "Fallback Brand"),
            ("contact:phone", "0812345678"),
            ("contact:website", "https://www.Example.com/menu"),
            ("name", "Primary Name"),
            ("phone", "0000000000"),
            ("website", "https://ignored.example"),
        ),
        longitude_e7=988_000_000,
        latitude_e7=188_000_000,
    )
    context = NormalizationContext(country_code="TH", calling_code="66")
    raw = raw_business_fields_from_parsed_object(
        region_id="thailand",
        parsed_object=parsed,
        context=context,
    )
    assert raw.name == "Primary Name"
    assert raw.website == "https://www.Example.com/menu"
    assert raw.phone == "0812345678"
    assert raw.address == "12 Nimman Road"
    assert raw.longitude_e7 == 988_000_000
    normalized = normalize_business(raw, context)
    assert normalized.name == "primary name"
    assert normalized.domain == "example.com"
    assert normalized.phone == "+66812345678"
    assert normalized.address == "12 nimman road"
    assert normalized.city == "chiang mai"
    assert normalized.country == "TH"


def test_parsed_object_collision_uses_canonical_lowercase_key() -> None:
    parsed = ParsedOsmObject(
        identity=OsmIdentity(OsmObjectType.NODE, 7),
        version=1,
        tags=(("Name", "Wrong Name"), ("name", "Primary Name")),
        longitude_e7=1,
        latitude_e7=1,
    )

    raw = raw_business_fields_from_parsed_object(
        region_id="ireland",
        parsed_object=parsed,
        context=NormalizationContext(country_code="IE", calling_code="353"),
    )

    payload = b'[["Name","Wrong Name"],["name","Primary Name"]]'
    expected_hash = hashlib.sha256(payload).hexdigest()
    assert raw.name == "Primary Name"
    assert raw.source_tags_hash == expected_hash


def test_identity_hash_ignores_source_object_identity_and_raw_formatting() -> None:
    context = NormalizationContext(country_code="AU", calling_code="61")
    first = RawBusinessFields(
        identity=RegionScopedIdentity("australia", OsmObjectType.NODE, 1),
        name="Example & Co.",
        website="https://www.example.com/path",
        phone="02 1234 5678",
        address="1 Main St.",
        city="Sydney",
        postcode="2000",
        country="AU",
        source_tags_hash=_SOURCE_HASH,
    )
    second = RawBusinessFields(
        identity=RegionScopedIdentity("australia", OsmObjectType.WAY, 2),
        name=" example and co ",
        website="EXAMPLE.COM",
        phone="(02) 1234-5678",
        address="1 Main Street",
        city="Sydney",
        postcode="2000",
        country="au",
        source_tags_hash=_SOURCE_HASH,
    )
    first_normalized = normalize_business(first, context)
    second_normalized = normalize_business(second, context)
    assert first_normalized.domain == second_normalized.domain
    assert first_normalized.phone == second_normalized.phone
    assert first_normalized.name == second_normalized.name
    assert first_normalized.identity_hash != second_normalized.identity_hash
    assert first_normalized.address == "1 main st"
    assert second_normalized.address == "1 main street"
