"""Per-project sweep orchestration for the daemon.

Wraps existing sweep functions with error isolation and event publishing.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any

import asyncpg
from redis.asyncio import Redis

from loom.bus import channels
from loom.config import LoomConfig
from loom.daemon.inbox import publish_daemon_event
from loom.graph.deps import sweep_epic_completion
from loom.orchestration.retry import sweep_retryable_tasks
from loom.orchestration.sweeper import sweep_expired_claims

log = logging.getLogger(__name__)


@dataclass
class SweepResult:
    project_id: str
    expired_claims: int = 0
    tasks_retried: int = 0
    epics_closed: int = 0
    notes_routed: int = 0
    error: str | None = None


async def sweep_project(
    pool: asyncpg.Pool,
    redis: Redis,
    project_id: str,
    config: LoomConfig,
    weft_pool: Any = None,
    weft_embedder: Any = None,
) -> SweepResult:
    """Run all sweeps for a single project with error isolation.

    If weft_pool and weft_embedder are provided, also routes codebase_notes
    from completed tasks to Weft.

    Never raises — captures errors into SweepResult.error.
    """
    result = SweepResult(project_id=project_id)

    try:
        result.expired_claims = await sweep_expired_claims(pool, redis, project_id)
        result.tasks_retried = await sweep_retryable_tasks(pool, redis, project_id)
        closed = await sweep_epic_completion(pool, redis, project_id)
        result.epics_closed = len(closed)

        # Route codebase_notes to Weft if configured
        if weft_pool and weft_embedder:
            weft_project_id = config.integrations.weft.project_id or None
            result.notes_routed = await process_codebase_notes(
                pool, redis, project_id,
                weft_pool, weft_embedder,
                weft_project_id=weft_project_id,
            )
    except Exception as e:
        log.exception("Sweep failed for project %s", project_id)
        result.error = str(e)
        return result

    # Publish a summary event if any work was done
    total_actions = result.expired_claims + result.tasks_retried + result.epics_closed
    if total_actions > 0:
        try:
            await publish_daemon_event(
                redis,
                project_id=project_id,
                event_type="sweep_complete",
                details={
                    "expired_claims": result.expired_claims,
                    "tasks_retried": result.tasks_retried,
                    "epics_closed": result.epics_closed,
                    "timestamp": datetime.now(timezone.utc).isoformat(),
                },
            )
        except Exception:
            log.warning("Failed to publish sweep event for project %s", project_id)

    return result


async def process_codebase_notes(
    pool: asyncpg.Pool,
    redis: Redis,
    project_id: str,
    weft_pool: Any,
    weft_embedder: Any,
    weft_project_id: str | None = None,
) -> int:
    """Find completed tasks with codebase_notes in output, route to Weft.

    Tracks processed task IDs in Redis so notes are only routed once.
    Returns count of notes stored.
    """
    from loom.integrations.weft import route_codebase_notes

    processed_key = channels.daemon_notes_processed(project_id)
    total_stored = 0

    async with pool.acquire() as conn:
        rows = await conn.fetch(
            """
            SELECT id, output FROM tasks
            WHERE project_id = $1::uuid
              AND status = 'done'
              AND output::text LIKE '%codebase_notes%'
            ORDER BY done_at DESC NULLS LAST
            LIMIT 50
            """,
            project_id,
        )

    import json
    for row in rows:
        task_id = str(row["id"])

        # Skip already processed
        if await redis.sismember(processed_key, task_id):
            continue

        output = json.loads(row["output"]) if isinstance(row["output"], str) else row["output"]
        notes = output.get("codebase_notes")
        if not isinstance(notes, dict) or not notes:
            await redis.sadd(processed_key, task_id)
            continue

        stored = await route_codebase_notes(
            weft_pool, weft_embedder, notes,
            project_id=weft_project_id,
        )
        total_stored += stored
        await redis.sadd(processed_key, task_id)
        log.info(
            "Routed %d codebase_notes from task %s to Weft",
            stored, task_id,
        )

    return total_stored
