"""Tests for the Offer Optimist YAML importer."""

from __future__ import annotations

from pathlib import Path

import yaml
from sqlalchemy.orm import Session

from luca_assistant.db.importers import (
    CURRENCY_DISPLAY,
    ISSUER_MAP,
    NETWORK_MAP,
    POINT_VALUATION_CPP,
    OfferOptimistImporter,
)
from luca_assistant.db.models import Card, CardBenefit, CardOffer

# Sample YAML fixture matching Offer Optimist schema
SAMPLE_YAML = [
    {
        "cardId": "abc123",
        "name": "Sapphire Preferred",
        "issuer": "CHASE",
        "network": "VISA",
        "currency": "CHASE",
        "isBusiness": False,
        "annualFee": 95,
        "isAnnualFeeWaived": False,
        "universalCashbackPercent": 1,
        "url": "https://example.com/csp",
        "imageUrl": "/images/csp.jpg",
        "credits": [],
        "offers": [
            {
                "spend": 4000,
                "amount": [{"amount": 60000}],
                "days": 90,
                "credits": [],
            }
        ],
        "historicalOffers": [
            {
                "spend": 4000,
                "amount": [{"amount": 80000}],
                "days": 90,
                "credits": [],
            }
        ],
        "discontinued": False,
    },
    {
        "cardId": "def456",
        "name": "Delta SkyMiles Gold",
        "issuer": "AMERICAN_EXPRESS",
        "network": "AMERICAN_EXPRESS",
        "currency": "DELTA",
        "isBusiness": False,
        "annualFee": 150,
        "isAnnualFeeWaived": True,
        "universalCashbackPercent": 1,
        "url": "https://example.com/delta-gold",
        "imageUrl": "/images/delta-gold.jpg",
        "credits": [
            {"description": "First checked bag free", "value": 50, "weight": 0.5}
        ],
        "offers": [
            {
                "spend": 2000,
                "amount": [{"amount": 50000}],
                "days": 180,
                "credits": [],
            }
        ],
        "historicalOffers": [],
        "discontinued": False,
    },
    {
        "cardId": "ghi789",
        "name": "Everyday",
        "issuer": "AMERICAN_EXPRESS",
        "network": "AMERICAN_EXPRESS",
        "currency": "AMERICAN_EXPRESS",
        "isBusiness": False,
        "annualFee": 0,
        "isAnnualFeeWaived": False,
        "universalCashbackPercent": 1,
        "url": "https://example.com/everyday",
        "credits": [],
        "offers": [],
        "historicalOffers": [],
        "discontinued": True,
    },
]


def _write_fixture(tmp_path: Path) -> Path:
    path = tmp_path / "data.yaml"
    path.write_text(yaml.dump(SAMPLE_YAML, allow_unicode=True))
    return path


class TestMapping:
    def test_issuer_map_covers_all_known(self) -> None:
        for issuer in ["AMERICAN_EXPRESS", "CHASE", "CAPITAL_ONE", "CITI"]:
            assert issuer in ISSUER_MAP

    def test_network_map(self) -> None:
        assert NETWORK_MAP["VISA"] == "Visa"
        assert NETWORK_MAP["MASTERCARD"] == "Mastercard"
        assert NETWORK_MAP["AMERICAN_EXPRESS"] == "Amex"

    def test_currency_display(self) -> None:
        assert CURRENCY_DISPLAY["CHASE"] == "UR"
        assert CURRENCY_DISPLAY["AMERICAN_EXPRESS"] == "MR"
        assert CURRENCY_DISPLAY["DELTA"] == "SkyMiles"

    def test_point_valuations_positive(self) -> None:
        for currency, cpp in POINT_VALUATION_CPP.items():
            assert cpp > 0, f"{currency} has non-positive valuation"


class TestParse:
    def test_parse_cards(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        importer = OfferOptimistImporter()
        data = importer.parse(path)
        # 3 entries in fixture, but one is discontinued
        assert len(data) == 3

    def test_parse_bank_mapping(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        banks = {d["bank"] for d in data}
        assert "Chase" in banks
        assert "Amex" in banks

    def test_parse_offer(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        csp = next(d for d in data if d["name"] == "Sapphire Preferred")
        offer = csp["current_offer"]
        assert offer is not None
        assert offer["amount"] == 60000
        assert offer["bonus_amount"] == "60,000 UR"
        assert offer["spend_requirement"] == 4000
        assert offer["spend_timeframe_months"] == 3

    def test_parse_bonus_value_usd(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        csp = next(d for d in data if d["name"] == "Sapphire Preferred")
        # 60000 * 1.25 / 100 = 750
        assert csp["current_offer"]["bonus_value_usd"] == 750

    def test_parse_is_highest_known(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        csp = next(d for d in data if d["name"] == "Sapphire Preferred")
        # Historical max is 80000, current is 60000 → not highest
        assert csp["historical_max_amount"] == 80000
        assert csp["current_offer"]["amount"] < csp["historical_max_amount"]

    def test_parse_discontinued(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        everyday = next(d for d in data if d["name"] == "Everyday")
        assert everyday["discontinued"] is True
        assert everyday["is_active"] is False

    def test_parse_credits(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        delta = next(d for d in data if d["name"] == "Delta SkyMiles Gold")
        assert len(delta["credits"]) == 1
        assert delta["credits"][0]["annual_credit"] == 50
        assert delta["credits"][0]["category"] == "first checked bag free"

    def test_product_family_inference(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        csp = next(d for d in data if d["name"] == "Sapphire Preferred")
        assert csp["product_family"] == "Sapphire"
        delta = next(d for d in data if d["name"] == "Delta SkyMiles Gold")
        assert delta["product_family"] == "Delta"

    def test_parse_no_offer(self, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        data = OfferOptimistImporter().parse(path)
        everyday = next(d for d in data if d["name"] == "Everyday")
        assert everyday["current_offer"] is None


class TestImportCards:
    def test_import_creates_cards(self, clean_session: Session, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        importer = OfferOptimistImporter()
        data = importer.parse(path)
        result = importer.import_cards(clean_session, data)
        clean_session.flush()

        # Everyday is discontinued → skipped
        assert result.cards_created == 2
        assert result.skipped_discontinued == 1

        cards = clean_session.query(Card).all()
        assert len(cards) == 2

    def test_import_creates_offers(self, clean_session: Session, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        importer = OfferOptimistImporter()
        data = importer.parse(path)
        result = importer.import_cards(clean_session, data)
        clean_session.flush()

        assert result.offers_created == 2
        offers = clean_session.query(CardOffer).all()
        assert len(offers) == 2

    def test_import_creates_credit_benefits(self, clean_session: Session, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        importer = OfferOptimistImporter()
        data = importer.parse(path)
        result = importer.import_cards(clean_session, data)
        clean_session.flush()

        assert result.benefits_created == 1  # Delta Gold's "first checked bag free"
        benefit = clean_session.query(CardBenefit).first()
        assert benefit is not None
        assert benefit.annual_credit == 50

    def test_upsert_no_duplicates(self, clean_session: Session, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        importer = OfferOptimistImporter()
        data = importer.parse(path)

        # Import twice
        importer.import_cards(clean_session, data)
        clean_session.flush()
        result2 = importer.import_cards(clean_session, data)
        clean_session.flush()

        # Second import should update, not create
        assert result2.cards_created == 0
        assert result2.cards_updated == 2

        cards = clean_session.query(Card).all()
        assert len(cards) == 2

    def test_import_is_highest_known(self, clean_session: Session, tmp_path: Path) -> None:
        path = _write_fixture(tmp_path)
        importer = OfferOptimistImporter()
        data = importer.parse(path)
        importer.import_cards(clean_session, data)
        clean_session.flush()

        csp = clean_session.query(Card).filter(Card.name == "Sapphire Preferred").first()
        offer = clean_session.query(CardOffer).filter(CardOffer.card_id == csp.id).first()
        # 60000 < 80000 historical → not highest
        assert offer.is_highest_known is False

        delta = clean_session.query(Card).filter(Card.name == "Delta SkyMiles Gold").first()
        delta_offer = (
            clean_session.query(CardOffer).filter(CardOffer.card_id == delta.id).first()
        )
        # No historical → highest
        assert delta_offer.is_highest_known is True
