"""PDF Statement Import — Stories 9.1–9.6."""
import io
import json
from decimal import Decimal
from datetime import date

import pytest

from app.extensions import db as _db
from app.models.import_batch import ImportBatch
from app.models.account import Account
from app.models.category import Category
from app.models.merchant_mapping import MerchantMapping
from app.models.transaction import Transaction


# ── fixtures ─────────────────────────────────────────────────────────────────

def _make_account():
    acct = Account(name="Checking", type="checking", is_active=True)
    _db.session.add(acct)
    _db.session.commit()
    return acct


def _make_category(name="Groceries"):
    cat = Category(name=name, is_system=True, is_active=True)
    _db.session.add(cat)
    _db.session.commit()
    return cat


def _make_batch(filename="test.pdf", issuer="chase", status="pending"):
    batch = ImportBatch(filename=filename, issuer=issuer, status=status)
    _db.session.add(batch)
    _db.session.commit()
    return batch


def _make_committed_batch():
    batch = ImportBatch(filename="old.pdf", issuer="chase", status="committed",
                        row_count=5, duplicates_skipped=1)
    _db.session.add(batch)
    _db.session.commit()
    return batch


# ── Story 9.1: Import Wizard Shell ───────────────────────────────────────────

class TestUploadPage:
    def test_upload_returns_200(self, client, db):
        assert client.get("/import/").status_code == 200

    def test_upload_shows_supported_issuers(self, client, db):
        response = client.get("/import/")
        for issuer in [b"Chase", b"Bank of America", b"Discover"]:
            assert issuer in response.data

    def test_upload_form_has_file_field(self, client, db):
        response = client.get("/import/")
        assert b"pdf_file" in response.data or b"PDF" in response.data

    def test_upload_form_has_issuer_override(self, client, db):
        response = client.get("/import/")
        assert b"issuer" in response.data.lower()

    def test_upload_no_file_rerenders(self, client, db):
        response = client.post("/import/", data={})
        assert response.status_code == 200

    def test_active_page_is_import(self, client, db):
        response = client.get("/import/")
        assert b"import" in response.data.lower()


# ── Story 9.2: Issuer Detection ───────────────────────────────────────────────

class TestIssuerDetector:
    def test_detect_chase_from_text(self):
        from app.services.pdf_parsers.detector import detect_issuer
        issuer, conf = detect_issuer("JPMorgan Chase Bank statement for your account")
        assert issuer == "chase"
        assert conf >= 0.9

    def test_detect_bofa_from_text(self):
        from app.services.pdf_parsers.detector import detect_issuer
        issuer, conf = detect_issuer("Bank of America credit card statement")
        assert issuer == "bofa"
        assert conf >= 0.9

    def test_detect_discover_from_text(self):
        from app.services.pdf_parsers.detector import detect_issuer
        issuer, conf = detect_issuer("DISCOVER CARD account statement")
        assert issuer == "discover"
        assert conf >= 0.9

    def test_detect_apple_card_from_text(self):
        from app.services.pdf_parsers.detector import detect_issuer
        issuer, conf = detect_issuer("Apple Card issued by Goldman Sachs Bank")
        assert issuer == "apple_card"
        assert conf >= 0.8

    def test_unknown_text_returns_none(self):
        from app.services.pdf_parsers.detector import detect_issuer
        issuer, conf = detect_issuer("This is some random text with no issuer info")
        assert issuer is None
        assert conf == 0.0

    def test_empty_text_returns_none(self):
        from app.services.pdf_parsers.detector import detect_issuer
        issuer, conf = detect_issuer("")
        assert issuer is None

    def test_needs_confirmation_below_threshold(self):
        from app.services.pdf_parsers.detector import needs_confirmation
        assert needs_confirmation(0.65) is True

    def test_no_confirmation_above_threshold(self):
        from app.services.pdf_parsers.detector import needs_confirmation
        assert needs_confirmation(0.75) is False


# ── Story 9.3: Merchant Normalizer ───────────────────────────────────────────

class TestMerchantNormalizer:
    def test_normalizes_amazon(self):
        from app.services.merchant_normalizer import normalize
        assert normalize("AMZN*MARKETPLACE 04/22") == "Amazon"

    def test_normalizes_netflix(self):
        from app.services.merchant_normalizer import normalize
        assert normalize("NETFLIX.COM") == "Netflix"

    def test_normalizes_starbucks(self):
        from app.services.merchant_normalizer import normalize
        assert normalize("STARBUCKS #12345 SEATTLE WA") == "Starbucks"

    def test_strips_trailing_date(self):
        from app.services.merchant_normalizer import normalize
        result = normalize("SOME MERCHANT 04/22")
        assert "04/22" not in result

    def test_strips_trailing_state_code(self):
        from app.services.merchant_normalizer import normalize
        result = normalize("COFFEE SHOP SEATTLE WA")
        # Should produce something cleaner than raw
        assert result  # non-empty

    def test_handles_empty_string(self):
        from app.services.merchant_normalizer import normalize
        assert normalize("") == ""

    def test_user_mapping_override(self):
        from app.services.merchant_normalizer import apply_user_mappings
        mappings = [{"raw_pattern": "MY STORE", "normalized": "My Store"}]
        result = apply_user_mappings("MY STORE 123", mappings)
        assert result == "My Store"

    def test_user_mapping_no_match_returns_none(self):
        from app.services.merchant_normalizer import apply_user_mappings
        mappings = [{"raw_pattern": "OTHER STORE", "normalized": "Other"}]
        result = apply_user_mappings("MY STORE 123", mappings)
        assert result is None


# ── Story 9.4: Review + Commit ────────────────────────────────────────────────

class TestReviewPage:
    def test_review_returns_200(self, client, db):
        batch = _make_batch()
        assert client.get(f"/import/review/{batch.id}").status_code == 200

    def test_review_nonexistent_returns_404(self, client, db):
        assert client.get("/import/review/9999").status_code == 404

    def test_review_redirects_committed_batch(self, client, db):
        batch = _make_committed_batch()
        response = client.get(f"/import/review/{batch.id}", follow_redirects=False)
        assert response.status_code == 302
        assert "history" in response.headers["Location"]

    def test_review_shows_batch_issuer(self, client, db):
        batch = _make_batch(issuer="chase")
        response = client.get(f"/import/review/{batch.id}")
        assert b"Chase" in response.data

    def test_review_shows_filename(self, client, db):
        batch = _make_batch(filename="my_statement.pdf")
        response = client.get(f"/import/review/{batch.id}")
        assert b"my_statement.pdf" in response.data

    def test_commit_redirects_to_history(self, client, db):
        _make_account()
        batch = _make_batch()
        response = client.post(f"/import/commit/{batch.id}", follow_redirects=False)
        assert response.status_code == 302
        assert "history" in response.headers["Location"]

    def test_commit_updates_batch_status(self, client, db):
        _make_account()
        batch = _make_batch()
        client.post(f"/import/commit/{batch.id}")
        _db.session.refresh(batch)
        assert batch.status == "committed"

    def test_commit_already_committed_redirects(self, client, db):
        _make_account()
        batch = _make_committed_batch()
        response = client.post(f"/import/commit/{batch.id}", follow_redirects=True)
        assert response.status_code == 200


# ── Story 9.5: Auto-categorization & User Correction ─────────────────────────

class TestAutoCategorization:
    def test_user_mapping_saved_on_update(self, client, db):
        batch = _make_batch()
        cat = _make_category()

        # Simulate an update that saves a merchant→category mapping
        # (staging pipeline won't have rows in test, so we test the mapping directly)
        from app.blueprints.import_pdf.routes import _save_merchant_mapping
        with client.application.app_context():
            _save_merchant_mapping("Starbucks", cat.id)

        mapping = MerchantMapping.query.filter_by(normalized="Starbucks", user_confirmed=True).first()
        assert mapping is not None
        assert mapping.category_id == cat.id

    def test_user_mapping_updates_existing(self, client, db):
        cat1 = _make_category("Coffee")
        cat2 = _make_category("Dining")

        with client.application.app_context():
            from app.blueprints.import_pdf.routes import _save_merchant_mapping
            _save_merchant_mapping("Starbucks", cat1.id)
            _save_merchant_mapping("Starbucks", cat2.id)  # update

        assert MerchantMapping.query.filter_by(
            normalized="Starbucks", user_confirmed=True
        ).count() == 1
        mapping = MerchantMapping.query.filter_by(normalized="Starbucks").first()
        assert mapping.category_id == cat2.id


# ── Story 9.6: Import History ─────────────────────────────────────────────────

class TestImportHistory:
    def test_history_returns_200(self, client, db):
        assert client.get("/import/history").status_code == 200

    def test_history_shows_empty_state_when_none(self, client, db):
        response = client.get("/import/history")
        assert b"No imports yet" in response.data

    def test_history_shows_batch_filename(self, client, db):
        _make_batch(filename="my_statement.pdf")
        response = client.get("/import/history")
        assert b"my_statement.pdf" in response.data

    def test_history_shows_committed_status(self, client, db):
        _make_committed_batch()
        response = client.get("/import/history")
        assert b"Committed" in response.data

    def test_history_shows_pending_status(self, client, db):
        _make_batch()
        response = client.get("/import/history")
        assert b"Pending" in response.data

    def test_history_shows_issuer_label(self, client, db):
        _make_batch(issuer="bofa")
        response = client.get("/import/history")
        assert b"Bank of America" in response.data

    def test_history_shows_parse_error_count(self, client, db):
        batch = ImportBatch(
            filename="err.pdf", issuer="chase", status="committed",
            parse_errors_json=json.dumps([
                {"page_number": 1, "raw_text": "bad line", "reason": "No match", "parser_version": "1.0"}
            ])
        )
        _db.session.add(batch)
        _db.session.commit()
        response = client.get("/import/history")
        assert b"error" in response.data.lower()

    def test_history_shows_review_link_for_pending(self, client, db):
        batch = _make_batch()
        response = client.get("/import/history")
        assert f"/import/review/{batch.id}".encode() in response.data
