from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal

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

from app.db.base import Base
from app.models.financial_record import ExpenseClaim
from app.services.document_numbering import (
    DOCUMENT_NUMBER_EXTRACT_PATTERN,
    DOCUMENT_NUMBER_PATTERN,
    build_document_number,
    generate_unique_expense_claim_no,
    is_application_claim_no,
)


def build_session() -> Session:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(bind=engine)
    factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
    return factory()


def test_build_document_number_uses_kind_prefix_timestamp_and_token() -> None:
    timestamp = datetime(2026, 5, 25, 10, 30, 45, tzinfo=UTC)

    assert (
        build_document_number("application", timestamp=timestamp, token="7K3M9Q2P")
        == "A7K3M9Q2P"
    )
    assert (
        build_document_number("reimbursement", timestamp=timestamp, token="7K3M9Q2P")
        == "R7K3M9Q2P"
    )
    assert (
        build_document_number("audit", timestamp=timestamp, token="7K3M9Q2P")
        == "D7K3M9Q2P"
    )


def test_document_number_pattern_accepts_short_and_legacy_numbers() -> None:
    assert DOCUMENT_NUMBER_PATTERN.fullmatch("A7K3M9Q2P")
    assert DOCUMENT_NUMBER_PATTERN.fullmatch("R7K3M9Q2P")
    assert DOCUMENT_NUMBER_PATTERN.fullmatch("D7K3M9Q2P")
    assert DOCUMENT_NUMBER_PATTERN.fullmatch("AP-20260525103045-ABCDEFGH")
    assert DOCUMENT_NUMBER_PATTERN.fullmatch("RE-20260525103045-HGFEDCBA")


def test_document_number_extract_pattern_finds_short_and_legacy_numbers() -> None:
    query = "查看 A7K3M9Q2P、R7K3M9Q2P 和 AP-20260525103045-ABCDEFGH 的状态"

    assert [match.group(0) for match in DOCUMENT_NUMBER_EXTRACT_PATTERN.finditer(query)] == [
        "A7K3M9Q2P",
        "R7K3M9Q2P",
        "AP-20260525103045-ABCDEFGH",
    ]


def test_build_document_number_rejects_ambiguous_token_chars() -> None:
    timestamp = datetime(2026, 5, 25, 10, 30, 45, tzinfo=UTC)

    with pytest.raises(ValueError):
        build_document_number("application", timestamp=timestamp, token="ABCDEF10")


def test_generate_unique_expense_claim_no_retries_existing_candidate() -> None:
    timestamp = datetime(2026, 5, 25, 10, 30, 45, tzinfo=UTC)
    with build_session() as db:
        db.add(
            ExpenseClaim(
                claim_no="RABCDEFGH",
                employee_name="张三",
                department_name="市场部",
                project_code=None,
                expense_type="transport",
                reason="交通报销",
                location="深圳",
                amount=Decimal("10.00"),
                currency="CNY",
                invoice_count=1,
                occurred_at=timestamp,
                status="draft",
                approval_stage="待提交",
                risk_flags_json=[],
            )
        )
        db.commit()

        tokens = iter(["ABCDEFGH", "HGFEDCBA"])

        assert (
            generate_unique_expense_claim_no(
                db,
                "reimbursement",
                timestamp=timestamp,
                token_factory=lambda: next(tokens),
            )
            == "RHGFEDCBA"
        )


def test_is_application_claim_no_supports_new_and_legacy_prefixes() -> None:
    assert is_application_claim_no("A7K3M9Q2P")
    assert is_application_claim_no("AP-20260525103045-ABCDEFGH")
    assert is_application_claim_no("APP-20260525-ABC123")
    assert not is_application_claim_no("R7K3M9Q2P")
    assert not is_application_claim_no("RE-20260525103045-ABCDEFGH")
