"""项目（立项）Repository 协议 + SQLAlchemy async 实现（ARCH §3.5 / §7.2）。

记忆/记账依赖这里的 **Protocol**，单测注入内存 fake，运行时注入 SQLAlchemy 实现。
统一按 owner_id 过滤（单用户原型 stub，多租户化时由认证主体替换）。
视图是与 ORM 解耦的只读 Pydantic 快照——路由不碰 SQLAlchemy 行。
"""

from __future__ import annotations

import uuid
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Protocol

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from ww_db.models import Chapter, ChapterReview, Project


@dataclass(frozen=True)
class ProjectView:
    """只读项目快照（snake_case）。"""

    id: uuid.UUID
    title: str
    genre: str | None = None
    logline: str | None = None
    premise: str | None = None
    theme: str | None = None
    selling_points: list[Any] = field(default_factory=list)
    structure: str | None = None
    tone: str | None = None
    ending_type: str | None = None
    narrative_pov: str | None = None
    updated_at: datetime | None = None
    pending_review_count: int = 0


@dataclass(frozen=True)
class ProjectCreate:
    """立项向导写入字段（owner_id 由服务层补 stub）。"""

    title: str
    genre: str | None = None
    logline: str | None = None
    premise: str | None = None
    theme: str | None = None
    selling_points: list[Any] = field(default_factory=list)
    structure: str | None = None
    tone: str | None = None
    ending_type: str | None = None
    narrative_pov: str | None = None


class ProjectRepo(Protocol):
    """项目读写接口（按 owner_id 隔离）。"""

    async def create(self, owner_id: uuid.UUID, data: ProjectCreate) -> ProjectView: ...

    async def list_for_owner(self, owner_id: uuid.UUID) -> list[ProjectView]: ...

    async def get(self, owner_id: uuid.UUID, project_id: uuid.UUID) -> ProjectView | None: ...


def _to_view(
    row: Project,
    *,
    updated_at: datetime | None = None,
    pending_review_count: int = 0,
) -> ProjectView:
    return ProjectView(
        id=row.id,
        title=row.title,
        genre=row.genre,
        logline=row.logline,
        premise=row.premise,
        theme=row.theme,
        selling_points=list(row.selling_points or []),
        structure=row.structure,
        tone=row.tone,
        ending_type=row.ending_type,
        narrative_pov=row.narrative_pov,
        updated_at=updated_at or row.updated_at,
        pending_review_count=pending_review_count,
    )


class SqlProjectRepo:
    """SQLAlchemy 实现：写 `projects`，按 owner_id 过滤读取。"""

    def __init__(self, session: AsyncSession) -> None:
        self._s = session

    async def create(self, owner_id: uuid.UUID, data: ProjectCreate) -> ProjectView:
        row = Project(
            owner_id=owner_id,
            title=data.title,
            genre=data.genre,
            logline=data.logline,
            premise=data.premise,
            theme=data.theme,
            selling_points=list(data.selling_points),
            structure=data.structure,
            tone=data.tone,
            ending_type=data.ending_type,
            narrative_pov=data.narrative_pov,
        )
        self._s.add(row)
        await self._s.commit()
        await self._s.refresh(row)
        return _to_view(row)

    async def list_for_owner(self, owner_id: uuid.UUID) -> list[ProjectView]:
        projects = (
            (
                await self._s.execute(
                    select(Project).where(Project.owner_id == owner_id).order_by(Project.created_at)
                )
            )
            .scalars()
            .all()
        )
        project_ids = [p.id for p in projects]
        pending_counts = await self._pending_review_counts(project_ids)
        activity_times = await self._activity_times(project_ids)
        return [
            _to_view(
                p,
                updated_at=max_time(p.updated_at, activity_times.get(p.id)),
                pending_review_count=pending_counts.get(p.id, 0),
            )
            for p in projects
        ]

    async def get(self, owner_id: uuid.UUID, project_id: uuid.UUID) -> ProjectView | None:
        row = (
            await self._s.execute(
                select(Project).where(Project.owner_id == owner_id, Project.id == project_id)
            )
        ).scalar_one_or_none()
        if row is None:
            return None
        pending_counts = await self._pending_review_counts([project_id])
        activity_times = await self._activity_times([project_id])
        return _to_view(
            row,
            updated_at=max_time(row.updated_at, activity_times.get(project_id)),
            pending_review_count=pending_counts.get(project_id, 0),
        )

    async def _activity_times(self, project_ids: list[uuid.UUID]) -> dict[uuid.UUID, datetime]:
        if not project_ids:
            return {}

        chapter_rows = await self._s.execute(
            select(Chapter.project_id, func.max(Chapter.updated_at))
            .where(Chapter.project_id.in_(project_ids))
            .group_by(Chapter.project_id)
        )
        review_rows = await self._s.execute(
            select(ChapterReview.project_id, func.max(ChapterReview.created_at))
            .where(ChapterReview.project_id.in_(project_ids))
            .group_by(ChapterReview.project_id)
        )
        activity: dict[uuid.UUID, datetime] = {}
        for project_id, updated_at in [*chapter_rows.all(), *review_rows.all()]:
            if updated_at is None:
                continue
            latest = max_time(activity.get(project_id), updated_at)
            if latest is not None:
                activity[project_id] = latest
        return activity

    async def _pending_review_counts(self, project_ids: list[uuid.UUID]) -> dict[uuid.UUID, int]:
        if not project_ids:
            return {}

        latest_reviews = (
            select(
                ChapterReview.project_id.label("project_id"),
                ChapterReview.chapter_no.label("chapter_no"),
                func.max(ChapterReview.created_at).label("reviewed_at"),
            )
            .where(ChapterReview.project_id.in_(project_ids))
            .group_by(ChapterReview.project_id, ChapterReview.chapter_no)
            .subquery()
        )
        latest_accepted = (
            select(
                Chapter.project_id.label("project_id"),
                Chapter.chapter_no.label("chapter_no"),
                func.max(Chapter.updated_at).label("accepted_at"),
            )
            .where(Chapter.project_id.in_(project_ids), Chapter.status == "accepted")
            .group_by(Chapter.project_id, Chapter.chapter_no)
            .subquery()
        )
        stmt = (
            select(Chapter.project_id, func.count(Chapter.id))
            .outerjoin(
                latest_reviews,
                (latest_reviews.c.project_id == Chapter.project_id)
                & (latest_reviews.c.chapter_no == Chapter.chapter_no),
            )
            .outerjoin(
                latest_accepted,
                (latest_accepted.c.project_id == Chapter.project_id)
                & (latest_accepted.c.chapter_no == Chapter.chapter_no),
            )
            .where(
                Chapter.project_id.in_(project_ids),
                Chapter.status == "draft",
                (latest_accepted.c.accepted_at.is_(None))
                | (latest_accepted.c.accepted_at < Chapter.updated_at),
                (latest_reviews.c.reviewed_at.is_(None))
                | (latest_reviews.c.reviewed_at < Chapter.updated_at),
            )
            .group_by(Chapter.project_id)
        )
        rows = await self._s.execute(stmt)
        return {project_id: int(count) for project_id, count in rows.all()}


def max_time(left: datetime | None, right: datetime | None) -> datetime | None:
    if left is None:
        return right
    if right is None:
        return left
    return max(left, right)
