"""
Contract Repository
===================
AwoooP Phase 3: contract revision CRUD（append-only）
2026-05-04 ogt + Claude Sonnet 4.6（ADR-107/ADR-112）

設計原則：
- append-only：已 published 的 revision 不可修改
- active pointer 以 UPSERT 維護（awooop_active_revisions）
- outbox 事件在同一 transaction 寫入（ADR-113）
- RLS 透過 get_db_context() 自動套用
"""

from __future__ import annotations

from typing import Any
from uuid import UUID

import structlog
from sqlalchemy import select, update
from sqlalchemy.dialects.postgresql import insert as pg_insert

from src.db.awooop_models import (
    AwoooPActiveRevision,
    AwoooPContractOutbox,
    AwoooPContractRevision,
)
from src.db.base import get_db_context

logger = structlog.get_logger(__name__)


# ─────────────────────────────────────────────────────────────────────────────
# Read
# ─────────────────────────────────────────────────────────────────────────────

async def get_revision(
    revision_id: UUID,
    project_id: str = "awoooi",
) -> AwoooPContractRevision | None:
    """依 revision_id 讀取單筆（含 RLS 驗證）"""
    async with get_db_context(project_id) as db:
        result = await db.execute(
            select(AwoooPContractRevision).where(
                AwoooPContractRevision.revision_id == revision_id,
                AwoooPContractRevision.project_id == project_id,
            )
        )
        return result.scalar_one_or_none()


async def get_active_revision(
    project_id: str,
    contract_family: str,
    contract_id: str,
) -> AwoooPContractRevision | None:
    """
    讀取 active revision（runtime 路徑）。
    只返回 lifecycle_status='active' 的 revision。
    """
    async with get_db_context(project_id) as db:
        result = await db.execute(
            select(AwoooPContractRevision)
            .join(
                AwoooPActiveRevision,
                AwoooPActiveRevision.active_revision_id == AwoooPContractRevision.revision_id,
            )
            .where(
                AwoooPActiveRevision.project_id == project_id,
                AwoooPActiveRevision.contract_family == contract_family,
                AwoooPActiveRevision.contract_id == contract_id,
                AwoooPContractRevision.lifecycle_status == "active",
            )
        )
        return result.scalar_one_or_none()


async def list_revisions(
    project_id: str,
    contract_family: str,
    contract_id: str,
    lifecycle_status: str | None = None,
) -> list[AwoooPContractRevision]:
    """列出所有 revision（按 version 降序）"""
    async with get_db_context(project_id) as db:
        q = select(AwoooPContractRevision).where(
            AwoooPContractRevision.project_id == project_id,
            AwoooPContractRevision.contract_family == contract_family,
            AwoooPContractRevision.contract_id == contract_id,
        )
        if lifecycle_status:
            q = q.where(AwoooPContractRevision.lifecycle_status == lifecycle_status)
        q = q.order_by(
            AwoooPContractRevision.version_major.desc(),
            AwoooPContractRevision.version_minor.desc(),
        )
        result = await db.execute(q)
        return list(result.scalars().all())


# ─────────────────────────────────────────────────────────────────────────────
# Write（append-only）
# ─────────────────────────────────────────────────────────────────────────────

async def create_draft(
    *,
    project_id: str,
    contract_family: str,
    contract_id: str,
    version_major: int,
    version_minor: int,
    body_json: dict[str, Any],
    body_hash: str,
    body_schema_version: str = "v1.0",
) -> AwoooPContractRevision:
    """建立 draft revision（不可被 runtime 讀取）"""
    async with get_db_context(project_id) as db:
        revision = AwoooPContractRevision(
            project_id=project_id,
            contract_family=contract_family,
            contract_id=contract_id,
            version_major=version_major,
            version_minor=version_minor,
            lifecycle_status="draft",
            body_json=body_json,
            body_hash=body_hash,
            body_schema_version=body_schema_version,
        )
        db.add(revision)
        await db.flush()
        await db.refresh(revision)

        logger.info(
            "contract_draft_created",
            revision_id=str(revision.revision_id),
            project_id=project_id,
            contract_family=contract_family,
            contract_id=contract_id,
        )
        return revision


async def mark_published(
    *,
    revision_id: UUID,
    project_id: str,
    publisher_id: str,
    publish_signature: str,
    published_at: Any,  # datetime
) -> AwoooPContractRevision:
    """
    draft → published 轉換（HMAC 簽章驗證後由 service 呼叫）。
    published revision 可被 activate，但不可被 runtime 直接讀取。
    """
    async with get_db_context(project_id) as db:
        await db.execute(
            update(AwoooPContractRevision)
            .where(
                AwoooPContractRevision.revision_id == revision_id,
                AwoooPContractRevision.project_id == project_id,
                AwoooPContractRevision.lifecycle_status == "draft",
            )
            .values(
                lifecycle_status="published",
                publisher_id=publisher_id,
                publish_signature=publish_signature,
                published_at=published_at,
            )
        )
        result = await db.execute(
            select(AwoooPContractRevision).where(
                AwoooPContractRevision.revision_id == revision_id
            )
        )
        revision = result.scalar_one()
        logger.info(
            "contract_published",
            revision_id=str(revision_id),
            project_id=project_id,
            publisher_id=publisher_id,
        )
        return revision


async def mark_active(
    *,
    revision_id: UUID,
    project_id: str,
    contract_family: str,
    contract_id: str,
    old_revision_id: UUID | None,
) -> AwoooPContractRevision:
    """
    published → active 轉換 + 更新 active pointer + 寫入 outbox。
    三個操作在同一 transaction（ADR-113 transactional outbox）。
    """
    async with get_db_context(project_id) as db:
        # 1. 更新 revision lifecycle_status
        await db.execute(
            update(AwoooPContractRevision)
            .where(
                AwoooPContractRevision.revision_id == revision_id,
                AwoooPContractRevision.project_id == project_id,
                AwoooPContractRevision.lifecycle_status == "published",
            )
            .values(lifecycle_status="active")
        )

        # 2. UPSERT active pointer
        stmt = pg_insert(AwoooPActiveRevision).values(
            project_id=project_id,
            contract_family=contract_family,
            contract_id=contract_id,
            active_revision_id=revision_id,
        )
        stmt = stmt.on_conflict_do_update(
            constraint="uq_active_pointer",
            set_={
                "active_revision_id": revision_id,
            },
        )
        await db.execute(stmt)

        # 3. 寫入 outbox event（ADR-113）
        outbox_event = AwoooPContractOutbox(
            event_type="contract.activated",
            project_id=project_id,
            contract_family=contract_family,
            contract_id=contract_id,
            old_revision_id=old_revision_id,
            new_revision_id=revision_id,
        )
        db.add(outbox_event)

        # 4. 如有舊 active revision，標記為 revoked
        if old_revision_id:
            await db.execute(
                update(AwoooPContractRevision)
                .where(
                    AwoooPContractRevision.revision_id == old_revision_id,
                    AwoooPContractRevision.lifecycle_status == "active",
                )
                .values(lifecycle_status="revoked")
            )

        result = await db.execute(
            select(AwoooPContractRevision).where(
                AwoooPContractRevision.revision_id == revision_id
            )
        )
        revision = result.scalar_one()

        logger.info(
            "contract_activated",
            revision_id=str(revision_id),
            old_revision_id=str(old_revision_id) if old_revision_id else None,
            project_id=project_id,
            contract_family=contract_family,
            contract_id=contract_id,
        )
        return revision
