from __future__ import annotations

from sqlalchemy import or_, select
from sqlalchemy.orm import Session

from app.models.agent_asset import (
    AgentAsset,
    AgentAssetReview,
    AgentAssetRuleFeedback,
    AgentAssetTestRun,
    AgentAssetVersion,
)
from app.services.agent_asset_access import AgentAssetAccessScope
from app.services.pagination import PageResult, paginate_select


class AgentAssetRepository:
    def __init__(
        self,
        db: Session,
        *,
        access_scope: AgentAssetAccessScope | None = None,
    ) -> None:
        self.db = db
        # 没有可信用户上下文时只允许系统维护平台资产，绝不能退回全表查询。
        self.access_scope = access_scope or AgentAssetAccessScope(
            tenant_id="__internal_no_tenant__",
            is_platform_admin=True,
        )

    def _visible(self, stmt, model):
        return stmt.where(self.access_scope.visibility_clause(model))

    def _require_write(self, asset: AgentAsset) -> None:
        self.access_scope.require_write(asset)

    def _prepare_asset_child(self, child):
        asset = self.get(str(child.asset_id or ""))
        if asset is None:
            raise LookupError("Asset not found")
        self._require_write(asset)
        tenant_id = str(getattr(child, "tenant_id", "") or "").strip()
        scope = str(getattr(child, "scope", "") or "").strip()
        if tenant_id and tenant_id != asset.tenant_id:
            raise LookupError("Asset not found")
        if scope and scope != asset.scope:
            raise LookupError("Asset not found")
        child.tenant_id = asset.tenant_id
        child.scope = asset.scope
        return child

    def _prepare_tenant_evidence(self, child):
        asset = self.get(str(child.asset_id or ""))
        if asset is None:
            raise LookupError("Asset not found")
        tenant_id = str(getattr(child, "tenant_id", "") or "").strip()
        scope = str(getattr(child, "scope", "") or "").strip()
        expected_tenant = self.access_scope.tenant_id
        if expected_tenant != "__internal_no_tenant__":
            if tenant_id and tenant_id != expected_tenant:
                raise LookupError("Asset not found")
            if scope and scope != "tenant":
                raise LookupError("Asset not found")
            child.tenant_id = expected_tenant
            child.scope = "tenant"
            return child
        if asset.scope != "platform" or asset.tenant_id != "platform":
            raise LookupError("Asset not found")
        child.tenant_id = "platform"
        child.scope = "platform"
        return child

    def _list_stmt(
        self,
        *,
        asset_type: str | None = None,
        status: str | None = None,
        domain: str | None = None,
        keyword: str | None = None,
    ):
        stmt = self._visible(select(AgentAsset), AgentAsset)

        if asset_type:
            stmt = stmt.where(AgentAsset.asset_type == asset_type)
        if status:
            stmt = stmt.where(AgentAsset.status == status)
        if domain:
            stmt = stmt.where(AgentAsset.domain == domain)
        if keyword:
            like_keyword = f"%{keyword.strip()}%"
            stmt = stmt.where(
                or_(
                    AgentAsset.name.ilike(like_keyword),
                    AgentAsset.code.ilike(like_keyword),
                    AgentAsset.description.ilike(like_keyword),
                )
            )

        stmt = stmt.order_by(AgentAsset.updated_at.desc(), AgentAsset.created_at.desc())
        return stmt

    def list(
        self,
        *,
        asset_type: str | None = None,
        status: str | None = None,
        domain: str | None = None,
        keyword: str | None = None,
    ) -> list[AgentAsset]:
        stmt = self._list_stmt(
            asset_type=asset_type,
            status=status,
            domain=domain,
            keyword=keyword,
        )
        return list(self.db.scalars(stmt).all())

    def list_page(
        self,
        *,
        asset_type: str | None = None,
        status: str | None = None,
        domain: str | None = None,
        keyword: str | None = None,
        page: int | None,
        page_size: int | None,
    ) -> PageResult[AgentAsset]:
        stmt = self._list_stmt(
            asset_type=asset_type,
            status=status,
            domain=domain,
            keyword=keyword,
        )
        return paginate_select(self.db, stmt, page=page, page_size=page_size)

    def get(self, asset_id: str) -> AgentAsset | None:
        stmt = self._visible(
            select(AgentAsset).where(AgentAsset.id == asset_id),
            AgentAsset,
        )
        return self.db.scalar(stmt)

    def get_by_code(self, code: str) -> AgentAsset | None:
        stmt = self._visible(
            select(AgentAsset).where(AgentAsset.code == code),
            AgentAsset,
        ).order_by(AgentAsset.scope.desc())
        return self.db.scalar(stmt)

    def list_versions(self, asset_id: str, *, limit: int | None = None) -> list[AgentAssetVersion]:
        stmt = self._visible(
            select(AgentAssetVersion)
            .where(AgentAssetVersion.asset_id == asset_id),
            AgentAssetVersion,
        ).order_by(AgentAssetVersion.created_at.desc())
        if limit is not None:
            stmt = stmt.limit(limit)
        return list(self.db.scalars(stmt).all())

    def list_versions_for_assets(self, asset_ids: list[str]) -> list[AgentAssetVersion]:
        if not asset_ids:
            return []

        stmt = self._visible(
            select(AgentAssetVersion)
            .where(AgentAssetVersion.asset_id.in_(asset_ids)),
            AgentAssetVersion,
        ).order_by(AgentAssetVersion.asset_id, AgentAssetVersion.created_at.desc())
        return list(self.db.scalars(stmt).all())

    def get_version(self, asset_id: str, version: str) -> AgentAssetVersion | None:
        stmt = self._visible(
            select(AgentAssetVersion).where(
                AgentAssetVersion.asset_id == asset_id,
                AgentAssetVersion.version == version,
            ),
            AgentAssetVersion,
        )
        return self.db.scalar(stmt)

    def list_reviews(self, asset_id: str, *, limit: int | None = None) -> list[AgentAssetReview]:
        stmt = self._visible(
            select(AgentAssetReview)
            .where(AgentAssetReview.asset_id == asset_id),
            AgentAssetReview,
        ).order_by(AgentAssetReview.created_at.desc())
        if limit is not None:
            stmt = stmt.limit(limit)
        return list(self.db.scalars(stmt).all())

    def list_reviews_for_assets(self, asset_ids: list[str]) -> list[AgentAssetReview]:
        if not asset_ids:
            return []

        stmt = self._visible(
            select(AgentAssetReview)
            .where(AgentAssetReview.asset_id.in_(asset_ids)),
            AgentAssetReview,
        ).order_by(AgentAssetReview.asset_id, AgentAssetReview.created_at.desc())
        return list(self.db.scalars(stmt).all())

    def get_review(
        self, asset_id: str, version: str, review_status: str | None = None
    ) -> AgentAssetReview | None:
        stmt = self._visible(
            select(AgentAssetReview).where(
                AgentAssetReview.asset_id == asset_id,
                AgentAssetReview.version == version,
            ),
            AgentAssetReview,
        )
        if review_status:
            stmt = stmt.where(AgentAssetReview.review_status == review_status)
        stmt = stmt.order_by(AgentAssetReview.created_at.desc())
        return self.db.scalar(stmt)

    def create_asset(self, asset: AgentAsset) -> AgentAsset:
        self._require_write(asset)
        self.db.add(asset)
        self.db.commit()
        self.db.refresh(asset)
        return asset

    def save_asset(self, asset: AgentAsset) -> AgentAsset:
        self._require_write(asset)
        self.db.add(asset)
        self.db.commit()
        self.db.refresh(asset)
        return asset

    def create_version(self, version: AgentAssetVersion) -> AgentAssetVersion:
        self._prepare_asset_child(version)
        self.db.add(version)
        self.db.commit()
        self.db.refresh(version)
        return version

    def create_review(self, review: AgentAssetReview) -> AgentAssetReview:
        self._prepare_asset_child(review)
        self.db.add(review)
        self.db.commit()
        self.db.refresh(review)
        return review

    def list_test_runs(
        self,
        asset_id: str,
        *,
        version: str | None = None,
        test_type: str | None = None,
        status: str | None = None,
        limit: int | None = None,
    ) -> list[AgentAssetTestRun]:
        stmt = self._visible(
            select(AgentAssetTestRun)
            .where(AgentAssetTestRun.asset_id == asset_id),
            AgentAssetTestRun,
        ).order_by(AgentAssetTestRun.created_at.desc())
        if version:
            stmt = stmt.where(AgentAssetTestRun.version == version)
        if test_type:
            stmt = stmt.where(AgentAssetTestRun.test_type == test_type)
        if status:
            stmt = stmt.where(AgentAssetTestRun.status == status)
        if limit is not None:
            stmt = stmt.limit(limit)
        return list(self.db.scalars(stmt).all())

    def get_latest_test_run(
        self,
        asset_id: str,
        *,
        version: str | None = None,
        test_type: str | None = None,
        status: str | None = None,
    ) -> AgentAssetTestRun | None:
        items = self.list_test_runs(
            asset_id,
            version=version,
            test_type=test_type,
            status=status,
            limit=1,
        )
        return items[0] if items else None

    def create_test_run(self, test_run: AgentAssetTestRun) -> AgentAssetTestRun:
        self._prepare_tenant_evidence(test_run)
        self.db.add(test_run)
        self.db.commit()
        self.db.refresh(test_run)
        return test_run

    def list_rule_feedback(
        self,
        asset_id: str,
        *,
        version: str | None = None,
        status: str | None = None,
        limit: int | None = None,
    ) -> list[AgentAssetRuleFeedback]:
        stmt = self._visible(
            select(AgentAssetRuleFeedback)
            .where(AgentAssetRuleFeedback.asset_id == asset_id),
            AgentAssetRuleFeedback,
        ).order_by(AgentAssetRuleFeedback.created_at.desc())
        if version:
            stmt = stmt.where(AgentAssetRuleFeedback.version == version)
        if status:
            stmt = stmt.where(AgentAssetRuleFeedback.status == status)
        if limit is not None:
            stmt = stmt.limit(limit)
        return list(self.db.scalars(stmt).all())

    def create_rule_feedback(
        self,
        feedback: AgentAssetRuleFeedback,
    ) -> AgentAssetRuleFeedback:
        self._prepare_tenant_evidence(feedback)
        self.db.add(feedback)
        self.db.commit()
        self.db.refresh(feedback)
        return feedback

    def delete_asset(self, asset: AgentAsset) -> None:
        self._require_write(asset)
        self.db.delete(asset)
        self.db.commit()
