"""Project management service."""
import logging
from datetime import datetime, timezone

from sqlalchemy import func, select

from fabledassistant.models import async_session
from fabledassistant.models.note import Note
from fabledassistant.models.project import Project

logger = logging.getLogger(__name__)


async def create_project(
    user_id: int,
    title: str,
    description: str = "",
    goal: str = "",
    color: str | None = None,
    status: str = "active",
) -> Project:
    async with async_session() as session:
        project = Project(
            user_id=user_id,
            title=title,
            description=description,
            goal=goal,
            color=color,
            status=status,
        )
        session.add(project)
        await session.commit()
        await session.refresh(project)
        return project


async def get_project(user_id: int, project_id: int) -> Project | None:
    async with async_session() as session:
        result = await session.execute(
            select(Project).where(Project.id == project_id, Project.user_id == user_id)
        )
        return result.scalars().first()


async def get_project_by_title(user_id: int, title: str) -> Project | None:
    async with async_session() as session:
        result = await session.execute(
            select(Project).where(
                Project.user_id == user_id,
                func.lower(Project.title) == func.lower(title.strip()),
            ).limit(1)
        )
        return result.scalars().first()


async def get_or_create_project(user_id: int, title: str) -> Project:
    project = await get_project_by_title(user_id, title)
    if project:
        return project
    return await create_project(user_id, title=title)


async def list_projects(user_id: int, status: str | None = None) -> list[Project]:
    async with async_session() as session:
        query = select(Project).where(Project.user_id == user_id)
        if status:
            query = query.where(Project.status == status)
        query = query.order_by(Project.updated_at.desc())
        result = await session.execute(query)
        return list(result.scalars().all())


async def update_project(user_id: int, project_id: int, **fields: object) -> Project | None:
    import asyncio
    async with async_session() as session:
        result = await session.execute(
            select(Project).where(Project.id == project_id, Project.user_id == user_id)
        )
        project = result.scalars().first()
        if project is None:
            return None
        for key, value in fields.items():
            if hasattr(project, key):
                setattr(project, key, value)
        project.updated_at = datetime.now(timezone.utc)
        await session.commit()
        await session.refresh(project)
    asyncio.create_task(generate_project_summary(user_id, project_id))
    return project


async def generate_project_summary(user_id: int, project_id: int) -> None:
    """Generate an LLM summary for a project and persist it. Fire-and-forget safe."""
    try:
        async with async_session() as session:
            project = (await session.execute(
                select(Project).where(Project.id == project_id, Project.user_id == user_id)
            )).scalars().first()
            if project is None:
                return

            note_rows = (await session.execute(
                select(Note.title, Note.body)
                .where(Note.project_id == project_id, Note.user_id == user_id)
                .order_by(Note.updated_at.desc())
                .limit(10)
            )).all()

        title = project.title or ""
        description = project.description or ""
        goal = project.goal or ""
        note_snippets = "\n".join(
            f"- {r.title}: {(r.body or '')[:200]}" for r in note_rows
        )

        prompt = (
            f"Summarize this project in 3-4 sentences covering its purpose, themes, and content.\n"
            f"Title: {title}\nDescription: {description}\nGoal: {goal}\n"
            f"Recent notes:\n{note_snippets}"
        )

        from fabledassistant.services.llm import generate_completion
        from fabledassistant.config import Config
        from fabledassistant.services.settings import get_setting
        messages = [{"role": "user", "content": prompt}]
        bg_model = await get_setting(user_id, "background_model", Config.OLLAMA_BACKGROUND_MODEL)
        summary = await generate_completion(messages, model=bg_model, max_tokens=400, num_ctx=2048)
        if not summary:
            return

        async with async_session() as session:
            project = (await session.execute(
                select(Project).where(Project.id == project_id)
            )).scalars().first()
            if project:
                project.auto_summary = summary.strip()
                project.summary_updated_at = datetime.now(timezone.utc)
                await session.commit()
        logger.debug("Generated summary for project %d", project_id)
    except Exception:
        logger.debug("Failed to generate summary for project %d", project_id, exc_info=True)


# Cutoff: summaries older than this were generated by qwen2.5:3b, which
# produced broken output (token repetition, hallucinated topics, misspellings).
# Anything stamped before the switch to gemma3:4b on 2026-04-12 gets
# regenerated at startup so the whole project list ends up on the better model.
_BACKGROUND_MODEL_CUTOVER = datetime(2026, 4, 12, tzinfo=timezone.utc)


async def backfill_project_summaries() -> None:
    """Generate summaries for projects missing or predating the gemma3:4b cutover.

    Fire-and-forget: each summary runs as its own background task.
    """
    import asyncio
    from sqlalchemy import or_
    try:
        async with async_session() as session:
            rows = (await session.execute(
                select(Project.id, Project.user_id).where(
                    or_(
                        Project.auto_summary.is_(None),
                        Project.summary_updated_at.is_(None),
                        Project.summary_updated_at < _BACKGROUND_MODEL_CUTOVER,
                    )
                )
            )).all()
        for row in rows:
            asyncio.create_task(generate_project_summary(row.user_id, row.id))
    except Exception:
        logger.debug("backfill_project_summaries failed", exc_info=True)


async def delete_project(user_id: int, project_id: int) -> bool:
    async with async_session() as session:
        result = await session.execute(
            select(Project).where(Project.id == project_id, Project.user_id == user_id)
        )
        project = result.scalars().first()
        if project is None:
            return False
        # Unlink notes (cascade handled by DB ON DELETE SET NULL, but we delete project here)
        await session.delete(project)
        await session.commit()
        return True


async def get_project_summary(user_id: int, project_id: int) -> dict:
    """Return task counts by status, note count, and last activity."""
    async with async_session() as session:
        # Task counts by status
        task_rows = await session.execute(
            select(Note.status, func.count(Note.id))
            .where(
                Note.user_id == user_id,
                Note.project_id == project_id,
                Note.status.isnot(None),
            )
            .group_by(Note.status)
        )
        # Initialise all three lifecycle keys to 0 so consumers can sum them
        # safely without `?? 0` guards. Frontend interface declares all three
        # as required; rendering `undefined + N` yields NaN.
        task_counts: dict[str, int] = {"todo": 0, "in_progress": 0, "done": 0}
        for status, count in task_rows.fetchall():
            task_counts[status] = count

        # Note count (non-tasks)
        note_count_result = await session.scalar(
            select(func.count(Note.id)).where(
                Note.user_id == user_id,
                Note.project_id == project_id,
                Note.status.is_(None),
            )
        )
        note_count = note_count_result or 0

        # Last activity
        last_activity_result = await session.scalar(
            select(func.max(Note.updated_at)).where(
                Note.user_id == user_id,
                Note.project_id == project_id,
            )
        )

        from fabledassistant.services.milestones import get_project_milestone_summary
        milestone_summary = await get_project_milestone_summary(user_id, project_id)

        return {
            "task_counts": task_counts,
            "note_count": note_count,
            "last_activity": last_activity_result.isoformat() if last_activity_result else None,
            "milestone_summary": milestone_summary,
        }


# ---------------------------------------------------------------------------
# Shared-access variants (honour ProjectShare in addition to ownership)
# ---------------------------------------------------------------------------

async def get_project_for_user(
    accessing_user_id: int, project_id: int
) -> tuple[Project, str] | None:
    """Returns (project, permission) if user has any access, else None."""
    from fabledassistant.services.access import get_project_permission
    perm = await get_project_permission(accessing_user_id, project_id)
    if perm is None:
        return None
    async with async_session() as session:
        project = await session.get(Project, project_id)
    return (project, perm) if project else None


async def list_projects_for_user(user_id: int, status: str | None = None) -> list[dict]:
    """Owned projects + shared projects, each dict has 'permission' field."""
    from fabledassistant.models.group import GroupMembership
    from fabledassistant.models.share import ProjectShare
    from fabledassistant.services.access import PERMISSION_RANK

    owned = await list_projects(user_id, status)
    owned_ids = {p.id for p in owned}

    result = []
    for p in owned:
        d = p.to_dict() if hasattr(p, "to_dict") else {"id": p.id, "title": p.title}
        d["permission"] = "owner"
        result.append(d)

    async with async_session() as session:
        user_group_ids = (await session.execute(
            select(GroupMembership.group_id).where(GroupMembership.user_id == user_id)
        )).scalars().all()

        shared_direct = (await session.execute(
            select(ProjectShare).where(ProjectShare.shared_with_user_id == user_id)
        )).scalars().all()

        shared_group: list[ProjectShare] = []
        if user_group_ids:
            shared_group = (await session.execute(
                select(ProjectShare).where(
                    ProjectShare.shared_with_group_id.in_(user_group_ids)
                )
            )).scalars().all()

    seen: dict[int, str] = {}
    for share in list(shared_direct) + list(shared_group):
        if share.project_id in owned_ids:
            continue
        prev = seen.get(share.project_id)
        if prev is None or PERMISSION_RANK[share.permission] > PERMISSION_RANK[prev]:
            seen[share.project_id] = share.permission

    for pid, perm in seen.items():
        if status:
            async with async_session() as session:
                proj = await session.get(Project, pid)
            if not proj or proj.status != status:
                continue
        else:
            async with async_session() as session:
                proj = await session.get(Project, pid)
        if proj:
            d = proj.to_dict() if hasattr(proj, "to_dict") else {"id": proj.id, "title": proj.title}
            d["permission"] = perm
            d["is_shared"] = True
            result.append(d)

    return result
