from __future__ import annotations

from collections.abc import Iterator

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from config.database import Base
from models import PricingImpuritySettings, PricingMetalSettings, PricingSettings
from services.pricing import PricingMetadata
from services.repositories import (
    PricingSettingsRepository,
    ensure_default_pricing_settings,
)
from services.unit_of_work import UnitOfWork


@pytest.fixture()
def engine() -> Iterator:
    engine = create_engine("sqlite:///:memory:", future=True)
    Base.metadata.create_all(bind=engine)
    try:
        yield engine
    finally:
        Base.metadata.drop_all(bind=engine)


@pytest.fixture()
def session(engine) -> Iterator[Session]:
    TestingSession = sessionmaker(
        bind=engine, expire_on_commit=False, future=True)
    db = TestingSession()
    try:
        yield db
    finally:
        db.close()


def test_pricing_settings_repository_crud(session: Session) -> None:
    repo = PricingSettingsRepository(session)

    settings = PricingSettings(
        name="Contract A",
        slug="Contract-A",
        default_currency="usd",
        default_payable_pct=95.0,
        moisture_threshold_pct=7.5,
        moisture_penalty_per_pct=1500.0,
    )
    repo.create(settings)

    metal_override = PricingMetalSettings(
        metal_code="Copper",
        payable_pct=96.0,
        moisture_threshold_pct=None,
        moisture_penalty_per_pct=None,
    )
    repo.attach_metal_override(settings, metal_override)

    impurity_override = PricingImpuritySettings(
        impurity_code="as",
        threshold_ppm=100.0,
        penalty_per_ppm=3.5,
    )
    repo.attach_impurity_override(settings, impurity_override)

    retrieved = repo.get_by_slug("CONTRACT-A", include_children=True)
    assert retrieved.slug == "contract-a"
    assert retrieved.default_currency == "USD"
    assert len(retrieved.metal_overrides) == 1
    assert retrieved.metal_overrides[0].metal_code == "copper"
    assert len(retrieved.impurity_overrides) == 1
    assert retrieved.impurity_overrides[0].impurity_code == "AS"

    listed = repo.list(include_children=True)
    assert len(listed) == 1
    assert listed[0].id == settings.id


def test_ensure_default_pricing_settings_creates_and_updates(session: Session) -> None:
    repo = PricingSettingsRepository(session)

    metadata_initial = PricingMetadata(
        default_payable_pct=100.0,
        default_currency="USD",
        moisture_threshold_pct=8.0,
        moisture_penalty_per_pct=0.0,
        impurity_thresholds={"As": 50.0},
        impurity_penalty_per_ppm={"As": 2.0},
    )

    result_create = ensure_default_pricing_settings(
        repo,
        metadata=metadata_initial,
        name="Seeded Pricing",
        description="Seeded from defaults",
    )

    assert result_create.created is True
    assert result_create.settings.slug == "default"
    assert result_create.settings.default_currency == "USD"
    assert len(result_create.settings.impurity_overrides) == 1
    assert result_create.settings.impurity_overrides[0].penalty_per_ppm == 2.0

    metadata_update = PricingMetadata(
        default_payable_pct=97.0,
        default_currency="EUR",
        moisture_threshold_pct=6.5,
        moisture_penalty_per_pct=250.0,
        impurity_thresholds={"As": 45.0, "Pb": 12.0},
        impurity_penalty_per_ppm={"As": 3.0, "Pb": 1.25},
    )

    result_update = ensure_default_pricing_settings(
        repo,
        metadata=metadata_update,
        name="Seeded Pricing",
        description="Seeded from defaults",
    )

    assert result_update.created is False
    assert result_update.updated_fields > 0
    assert result_update.impurity_upserts >= 1

    updated = repo.get_by_slug("default", include_children=True)
    assert updated.default_currency == "EUR"
    as_override = {
        item.impurity_code: item for item in updated.impurity_overrides}["AS"]
    assert float(as_override.threshold_ppm) == 45.0
    assert float(as_override.penalty_per_ppm) == 3.0
    pb_override = {
        item.impurity_code: item for item in updated.impurity_overrides}["PB"]
    assert float(pb_override.threshold_ppm) == 12.0


def test_unit_of_work_exposes_pricing_settings(engine) -> None:
    TestingSession = sessionmaker(
        bind=engine, expire_on_commit=False, future=True)
    metadata = PricingMetadata(
        default_payable_pct=99.0,
        default_currency="USD",
        moisture_threshold_pct=7.0,
        moisture_penalty_per_pct=125.0,
        impurity_thresholds={"Zn": 80.0},
        impurity_penalty_per_ppm={"Zn": 0.5},
    )

    with UnitOfWork(session_factory=TestingSession) as uow:
        assert uow.pricing_settings is not None
        result = uow.ensure_default_pricing_settings(
            metadata=metadata,
            slug="contract-core",
            name="Contract Core",
        )
        assert result.settings.slug == "contract-core"
        assert result.created is True

    with UnitOfWork(session_factory=TestingSession) as uow:
        assert uow.pricing_settings is not None
        stored = uow.pricing_settings.get_by_slug(
            "contract-core", include_children=True)
        assert stored.default_payable_pct == 99.0
        assert stored.impurity_overrides[0].impurity_code == "ZN"


def test_unit_of_work_get_pricing_metadata_returns_defaults(engine) -> None:
    TestingSession = sessionmaker(
        bind=engine, expire_on_commit=False, future=True)
    seeded_metadata = PricingMetadata(
        default_payable_pct=96.5,
        default_currency="aud",
        moisture_threshold_pct=6.25,
        moisture_penalty_per_pct=210.0,
        impurity_thresholds={"As": 45.0, "Pb": 15.0},
        impurity_penalty_per_ppm={"As": 1.75, "Pb": 0.9},
    )

    with UnitOfWork(session_factory=TestingSession) as uow:
        result = uow.ensure_default_pricing_settings(
            metadata=seeded_metadata,
            slug="default",
            name="Default Contract",
            description="Primary contract defaults",
        )
        assert result.created is True

    with UnitOfWork(session_factory=TestingSession) as uow:
        retrieved = uow.get_pricing_metadata()

    assert retrieved is not None
    assert retrieved.default_currency == "AUD"
    assert retrieved.default_payable_pct == 96.5
    assert retrieved.moisture_threshold_pct == 6.25
    assert retrieved.moisture_penalty_per_pct == 210.0
    assert retrieved.impurity_thresholds["AS"] == 45.0
    assert retrieved.impurity_thresholds["PB"] == 15.0
    assert retrieved.impurity_penalty_per_ppm["AS"] == 1.75
    assert retrieved.impurity_penalty_per_ppm["PB"] == 0.9


def test_unit_of_work_get_pricing_metadata_returns_none_when_missing(engine) -> None:
    TestingSession = sessionmaker(
        bind=engine, expire_on_commit=False, future=True)

    with UnitOfWork(session_factory=TestingSession) as uow:
        missing = uow.get_pricing_metadata(slug="non-existent")

    assert missing is None
