from __future__ import annotations

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

from app.core.agent_enums import AgentAssetDomain, AgentAssetStatus, AgentAssetType
from app.db.base import Base
from app.models.agent_asset import AgentAsset, AgentAssetTestRun
from app.services.agent_asset_release_scheduler import AgentAssetReleaseScheduler
from app.services.agent_asset_release_telemetry import (
    AgentAssetReleaseTelemetryService,
    ReleaseObservationInput,
)


def _asset(asset_id: str, tenant_id: str | None) -> AgentAsset:
    previous_config: dict[str, object] = {
        "detail_mode": "json_risk",
        "enabled": True,
        "stable_marker": f"stable:{asset_id}",
    }
    if tenant_id is not None:
        previous_config["tenant_id"] = tenant_id
    return AgentAsset(
        id=asset_id,
        tenant_id=tenant_id or "platform",
        scope="tenant" if tenant_id is not None else "platform",
        asset_type=AgentAssetType.RULE.value,
        code=f"risk.{asset_id}",
        name="周期发布监控规则",
        description="",
        domain=AgentAssetDomain.EXPENSE.value,
        scenario_json=["travel"],
        owner="finance",
        status=AgentAssetStatus.ACTIVE.value,
        current_version="v1",
        published_version="v1",
        working_version="v2",
        config_json={
            **previous_config,
            "release_guard": {
                "release_id": f"release-{asset_id}",
                "stage": "shadow",
                "candidate_version": "v2",
                "previous_version": "v1",
                "previous_config": previous_config,
                "policy": {
                    "shadow_min_samples": 1,
                    "canary_min_samples": 1,
                    "max_error_rate": 1.0,
                    "min_precision": 0.9,
                    "max_precision_drop": 1.0,
                    "canary_traffic_percent": 10,
                },
            },
        },
    )


def _false_positive_sample(db: Session, asset: AgentAsset, tenant_id: str) -> None:
    telemetry = AgentAssetReleaseTelemetryService(db)
    observation = telemetry.record_observation(
        ReleaseObservationInput(
            tenant_id=tenant_id,
            asset_id=asset.id,
            release_id=f"release-{asset.id}",
            stage="shadow",
            version="v2",
            rule_code=asset.code,
            source_key=f"claim-{asset.id}",
            candidate_hit=True,
            baseline_hit=True,
        )
    )
    telemetry.record_review_label(
        tenant_id=tenant_id,
        observation_id=observation.id,
        label="false_positive",
        request_id=f"review-{asset.id}",
        actor_id="trusted-reviewer",
    )


def test_scheduler_groups_tenants_rolls_back_and_skips_global_assets() -> None:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(engine)
    factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
    with factory() as db:
        tenant_asset = _asset("tenant-release-scheduler", "tenant-a")
        global_asset = _asset("global-release-scheduler", None)
        db.add_all([tenant_asset, global_asset])
        db.commit()
        _false_positive_sample(db, tenant_asset, "tenant-a")
        with pytest.raises(LookupError, match="Agent asset not found"):
            _false_positive_sample(db, global_asset, "tenant-a")
        db.commit()

    result = AgentAssetReleaseScheduler(session_factory=factory)._run_once()

    with factory() as db:
        tenant_asset = db.get(AgentAsset, "tenant-release-scheduler")
        global_asset = db.get(AgentAsset, "global-release-scheduler")
        assert tenant_asset is not None
        assert global_asset is not None
        assert tenant_asset.config_json["release_guard"]["stage"] == "rolled_back"
        assert global_asset.config_json["release_guard"]["stage"] == "shadow"
    engine.dispose()

    assert result == {
        "tenants": 1,
        "scanned": 1,
        "evaluated": 1,
        "collecting": 0,
        "rolled_back": 1,
        "errors": 0,
        "global_skipped": 1,
    }


def test_scheduler_cursor_rotates_beyond_fixed_batch_limit() -> None:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(engine)
    factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
    with factory() as db:
        first = _asset("cursor-a", "tenant-a")
        second = _asset("cursor-b", "tenant-a")
        db.add_all([first, second])
        db.commit()
        for asset in (first, second):
            telemetry = AgentAssetReleaseTelemetryService(db)
            observation = telemetry.record_observation(
                ReleaseObservationInput(
                    tenant_id="tenant-a",
                    asset_id=asset.id,
                    release_id=f"release-{asset.id}",
                    stage="shadow",
                    version="v2",
                    rule_code=asset.code,
                    source_key=f"claim-{asset.id}",
                    candidate_hit=True,
                    baseline_hit=True,
                )
            )
            telemetry.record_review_label(
                tenant_id="tenant-a",
                observation_id=observation.id,
                label="confirmed",
                request_id=f"review-{asset.id}",
                actor_id="trusted-reviewer",
            )
        db.commit()

    scheduler = AgentAssetReleaseScheduler(session_factory=factory)
    scheduler._batch_size = 1
    scheduler._run_once()
    scheduler._run_once()

    with factory() as db:
        evaluated = set(db.scalars(select(AgentAssetTestRun.asset_id)).all())
    engine.dispose()

    assert evaluated == {"cursor-a", "cursor-b"}
