from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal

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

import app.models  # noqa: F401 - 注册完整 metadata
from app.db.base_class import Base
from app.models.commercial_billing import CommercialAdminEvent, CommercialBillingPeriod
from app.schemas.commercial import CommercialPlanCreate, CommercialSubscriptionCreate
from app.services.commercial_admin import CommercialAdminService
from app.services.commercial_subscription_rollover import (
    CommercialSubscriptionRolloverService,
)


@pytest.fixture()
def db() -> Session:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(engine)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    with factory() as session:
        yield session
    Base.metadata.drop_all(engine)
    engine.dispose()


def test_subscription_creation_issues_period_and_redacted_audit(db: Session) -> None:
    start = datetime(2026, 1, 1, tzinfo=UTC)
    end = datetime(2026, 2, 1, tzinfo=UTC)
    _, subscription = _seed_subscription(
        db,
        "tenant-a",
        start=start,
        end=end,
        request_prefix="initial",
    )

    periods = list(
        db.scalars(
            select(CommercialBillingPeriod).where(CommercialBillingPeriod.tenant_id == "tenant-a")
        ).all()
    )
    assert len(periods) == 1
    assert periods[0].subscription_id == subscription.id
    assert periods[0].period_start.replace(tzinfo=UTC) == start
    assert periods[0].period_end.replace(tzinfo=UTC) == end
    assert periods[0].base_fee_snapshot == Decimal("100.0000")

    events = list(
        db.scalars(
            select(CommercialAdminEvent).where(CommercialAdminEvent.tenant_id == "tenant-a")
        ).all()
    )
    assert {event.action for event in events} >= {
        "plan_created",
        "plan_activated",
        "subscription_created",
        "billing_period_created",
    }
    serialized = str([(event.before_json, event.after_json) for event in events]).casefold()
    for forbidden in (
        "contract_terms_json",
        "metadata_json",
        "external_subscription_id",
        "secret",
        "password",
        "token",
    ):
        assert forbidden not in serialized


def test_auto_renew_rollover_is_idempotent_and_keeps_month_end_anchor(db: Session) -> None:
    start = datetime(2027, 1, 31, tzinfo=UTC)
    due = datetime(2027, 2, 28, tzinfo=UTC)
    _, subscription = _seed_subscription(
        db,
        "tenant-rollover",
        start=start,
        end=due,
        auto_renew=True,
        request_prefix="rollover",
    )

    first = CommercialSubscriptionRolloverService(db).rollover_due(
        "tenant-rollover",
        subscription.id,
        as_of=due,
    )
    replay = CommercialSubscriptionRolloverService(db).rollover_due(
        "tenant-rollover",
        subscription.id,
        as_of=due,
    )

    assert first.status == "rolled_over"
    assert len(first.created_period_ids) == 1
    assert first.current_period_start == due
    assert first.current_period_end == datetime(2027, 3, 31, tzinfo=UTC)
    assert replay.status == "not_due"
    periods = list(
        db.scalars(
            select(CommercialBillingPeriod)
            .where(CommercialBillingPeriod.subscription_id == subscription.id)
            .order_by(CommercialBillingPeriod.period_sequence)
        ).all()
    )
    assert [period.period_sequence for period in periods] == [1, 2]
    assert periods[0].period_start.replace(tzinfo=UTC) == start
    assert periods[0].period_end.replace(tzinfo=UTC) == due
    assert periods[1].source == "auto_renew"
    assert subscription.version == 2
    actions = set(
        db.scalars(
            select(CommercialAdminEvent.action).where(
                CommercialAdminEvent.tenant_id == "tenant-rollover"
            )
        ).all()
    )
    assert {"billing_period_created", "subscription_rolled_over"} <= actions


@pytest.mark.parametrize(
    ("interval", "contract_end", "reason_code"),
    [
        ("contract", None, "contract_interval_requires_explicit_renewal"),
        (
            "monthly",
            datetime(2026, 2, 15, tzinfo=UTC),
            "contract_boundary_requires_renewal",
        ),
    ],
)
def test_rollover_fails_closed_at_unprovable_contract_boundary(
    db: Session,
    interval: str,
    contract_end: datetime | None,
    reason_code: str,
) -> None:
    tenant_id = f"tenant-{interval}"
    _, subscription = _seed_subscription(
        db,
        tenant_id,
        start=datetime(2026, 1, 1, tzinfo=UTC),
        end=datetime(2026, 2, 1, tzinfo=UTC),
        billing_interval=interval,
        auto_renew=True,
        contract_end=contract_end,
        request_prefix=interval,
    )

    result = CommercialSubscriptionRolloverService(db).rollover_due(
        tenant_id,
        subscription.id,
        as_of=datetime(2026, 2, 1, tzinfo=UTC),
    )

    assert result.status == "ineligible"
    assert result.reason_code == reason_code
    assert (
        db.scalar(
            select(CommercialBillingPeriod.period_sequence).where(
                CommercialBillingPeriod.subscription_id == subscription.id
            )
        )
        == 1
    )


def _seed_subscription(
    db: Session,
    tenant_id: str,
    *,
    start: datetime,
    end: datetime,
    billing_interval: str = "monthly",
    auto_renew: bool = False,
    contract_end: datetime | None = None,
    request_prefix: str,
):
    admin = CommercialAdminService(db)
    plan = admin.create_plan(
        tenant_id,
        CommercialPlanCreate(
            plan_code="billing",
            name="不可变账期测试套餐",
            pricing_model="subscription",
            billing_interval=billing_interval,
            currency="CNY",
            base_fee=Decimal("100"),
            effective_from=start,
            contract_terms_json={"secret": "must-not-enter-audit"},
        ),
        actor_id="platform-admin",
        request_id=f"{request_prefix}-plan-create",
    )
    admin.activate_plan(
        tenant_id,
        plan.id,
        expected_version=plan.version,
        actor_id="platform-admin",
        request_id=f"{request_prefix}-plan-activate",
        reason="测试激活套餐",
    )
    subscription = admin.create_subscription(
        tenant_id,
        CommercialSubscriptionCreate(
            subscription_key=f"{tenant_id}-subscription",
            plan_id=plan.id,
            starts_at=start,
            ends_at=contract_end,
            current_period_start=start,
            current_period_end=end,
            seats=3,
            auto_renew=auto_renew,
            metadata_json={"token": "must-not-enter-audit"},
        ),
        actor_id="platform-admin",
        request_id=f"{request_prefix}-subscription-create",
    )
    db.flush()
    return plan, subscription
