from collections.abc import Iterator

import pytest
from moto import mock_aws

from domain.addresses.user_address import UserAddress
from domain.postcode import Postcode
from infrastructure.csv_s3_client import CsvS3Client
from repositories.user_address.user_address_csv_s3_repository import (
    UserAddressCsvS3Repository,
)
from tests.infrastructure import make_boto_client

BUCKET = "user-address-bucket"


@pytest.fixture
def repo() -> Iterator[UserAddressCsvS3Repository]:
    with mock_aws():
        boto_client = make_boto_client("s3")
        boto_client.create_bucket(Bucket=BUCKET)
        csv_client = CsvS3Client(boto_client, BUCKET)
        yield UserAddressCsvS3Repository(csv_client, BUCKET)


def _upload_csv(
    repo: UserAddressCsvS3Repository, rows: list[dict[str, str]], key: str
) -> str:
    return repo._csv_client.save_rows(rows, key)  # pyright: ignore[reportPrivateUsage]


def test_load_batch_parses_address_postcode_and_reference(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    rows = [
        {
            "Address 1": "1 High Street",
            "Address 2": "Flat 2",
            "Address 3": "Townville",
            "postcode": "sw1a 1aa",
            "Internal Reference": "REF-001",
        }
    ]
    uri = _upload_csv(repo, rows, "uploads/full.csv")

    # act
    addresses = repo.load_batch(uri)

    # assert
    assert len(addresses) == 1
    address = addresses[0]
    assert address.user_address == "1 High Street, Flat 2, Townville"
    assert address.postcode == Postcode("SW1A1AA")
    assert address.internal_reference == "REF-001"


def test_load_batch_uses_only_address_1_when_others_missing(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    rows = [
        {
            "Address 1": "10 Cardiff Road",
            "Address 2": "",
            "Address 3": "",
            "postcode": "CF10 1AA",
            "Internal Reference": "REF-002",
        }
    ]
    uri = _upload_csv(repo, rows, "uploads/address1-only.csv")

    # act
    addresses = repo.load_batch(uri)

    # assert
    assert len(addresses) == 1
    assert addresses[0].user_address == "10 Cardiff Road"
    assert addresses[0].postcode == Postcode("CF101AA")
    assert addresses[0].internal_reference == "REF-002"


def test_load_batch_handles_missing_internal_reference(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    rows = [
        {
            "Address 1": "5 Park Lane",
            "Address 2": "",
            "Address 3": "",
            "postcode": "M1 1AA",
            "Internal Reference": "",
        }
    ]
    uri = _upload_csv(repo, rows, "uploads/no-ref.csv")

    # act
    addresses = repo.load_batch(uri)

    # assert
    assert len(addresses) == 1
    assert addresses[0].user_address == "5 Park Lane"
    assert addresses[0].postcode == Postcode("M11AA")
    assert addresses[0].internal_reference is None


def test_load_batch_captures_full_source_row(
    repo: UserAddressCsvS3Repository,
) -> None:
    # A raw EPC-export-shaped row: the splitter must preserve every column,
    # not just the ones it parses into UserAddress fields.
    # arrange
    row = {
        "Asset Reference": "511",
        "Address 1": "9 Abingdon Road Padiham Lancashire BB12 7BX",
        "postcode": "BB12 7BX",
        "Property Type": "House: End Terrace",
        "SAP Score": "69",
    }
    uri = _upload_csv(repo, [row], "uploads/epc.csv")

    # act
    addresses = repo.load_batch(uri)

    # assert
    assert addresses[0].source_row == row


def test_load_batch_raises_when_postcode_column_absent(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    rows = [{"Address 1": "1 High Street", "Property Type": "Flat"}]
    uri = _upload_csv(repo, rows, "uploads/no-postcode.csv")

    # act / assert
    with pytest.raises(ValueError, match="no 'postcode' column"):
        repo.load_batch(uri)


def test_save_batch_passes_through_all_columns_and_appends_postcode_clean(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    row = {
        "Asset Reference": "511",
        "Address 1": "9 Abingdon Road Padiham Lancashire BB12 7BX",
        "postcode": " BB12 7BX",
        "Property Type": "House: End Terrace",
    }
    uri = _upload_csv(repo, [row], "uploads/epc.csv")
    addresses = repo.load_batch(uri)

    # act
    saved_uri = repo.save_batch(addresses, "tasks/passthrough")
    saved_rows = repo._csv_client.read_rows(saved_uri)  # pyright: ignore[reportPrivateUsage]

    # assert
    assert len(saved_rows) == 1
    saved = saved_rows[0]
    # Every original column survives, byte-for-byte.
    for column, value in row.items():
        assert saved[column] == value
    # Plus the one appended column the downstream address2uprn stage groups on.
    assert saved["postcode_clean"] == "BB127BX"


def test_save_batch_returns_uri_under_path_prefix(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    addresses = [
        UserAddress(
            user_address="1 High Street",
            postcode=Postcode("SW1A 1AA"),
            source_row={"Address 1": "1 High Street", "postcode": "SW1A 1AA"},
        ),
    ]

    # act
    uri = repo.save_batch(addresses, "tasks/abc/batches")

    # assert
    assert uri.startswith(f"s3://{BUCKET}/tasks/abc/batches/")
    assert uri.endswith(".csv")


def test_save_then_reload_round_trip_preserves_columns(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    rows = [
        {
            "Address 1": "1 High Street",
            "postcode": "SW1A 1AA",
            "Internal Reference": "REF-001",
        },
        {
            "Address 1": "2 Low Street",
            "postcode": "XY9 8ZW",
            "Internal Reference": "",
        },
    ]
    uri = _upload_csv(repo, rows, "uploads/round-trip.csv")
    addresses = repo.load_batch(uri)

    # act
    saved_uri = repo.save_batch(addresses, "tasks/round-trip")
    saved_rows = repo._csv_client.read_rows(saved_uri)  # pyright: ignore[reportPrivateUsage]

    # assert
    # Original columns come back verbatim; postcode_clean is the only addition.
    assert [
        {k: v for k, v in r.items() if k != "postcode_clean"} for r in saved_rows
    ] == rows
    assert [r["postcode_clean"] for r in saved_rows] == ["SW1A1AA", "XY98ZW"]


def test_save_batch_uses_unique_filename_per_call(
    repo: UserAddressCsvS3Repository,
) -> None:
    # arrange
    addresses = [
        UserAddress(
            user_address="1 High Street",
            postcode=Postcode("SW1A 1AA"),
            source_row={"Address 1": "1 High Street", "postcode": "SW1A 1AA"},
        ),
    ]

    # act
    uri_1 = repo.save_batch(addresses, "tasks/uniqueness")
    uri_2 = repo.save_batch(addresses, "tasks/uniqueness")

    # assert
    assert uri_1 != uri_2
