"""
oracle/router_v1.py
FastAPI router for all Oracle v1 endpoints.
Mounted at /api/oracle/v1 in main.py.

Endpoints (from spec §13.2):
  GET    /me
  GET    /canvas-pages/{pageId}
  POST   /canvas-pages/{pageId}/prompts
  POST   /canvas-pages/{pageId}/forks
  POST   /canvas-pages/{pageId}/rollback
  GET    /canvas-pages/{pageId}/revisions
  GET    /component-templates
  POST   /component-templates/synthesize (stub)
  GET    /merge-requests
  POST   /merge-requests
  POST   /merge-requests/{mrId}/review
  WS     /ws/oracle/canvas/{pageId}
"""
from __future__ import annotations

import json
import logging
import os
import uuid
from datetime import datetime, timezone
from typing import Any, Set

from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket, WebSocketDisconnect, status
from pydantic import BaseModel, Field

from backend.auth.dependencies import UserPrincipal, get_current_user
from .canvas_service import canvas_service
from .collaboration_service import collaboration_service
from .action_service import oracle_action_service
from .persona_service import persona_service
from .prompt_orchestrator import prompt_orchestrator
from .policy_service import PolicyService, PolicyContext
from .codebook_service import codebook_service

logger = logging.getLogger(__name__)

router = APIRouter()
policy_svc = PolicyService()
_DEFAULT_TENANT_ID = os.getenv("ORACLE_DEFAULT_TENANT_ID", "tenant_velocity")

# ── Helpers ───────────────────────────────────────────────────────────────────

def _ok(data: Any, meta: dict | None = None) -> dict:
    return {"status": "ok", "data": data, "meta": meta or {}}


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _normalize_oracle_role(role: str) -> str:
    mapping = {
        "JUNIOR_BROKER": "junior_broker",
        "SENIOR_BROKER": "senior_broker",
        "SALES_DIRECTOR": "sales_director",
        "ADMIN": "platform_admin",
    }
    return mapping.get(role.strip().upper(), "sales_director")


def _build_user_profile(
    *,
    user_id: str,
    email: str,
    display_name: str,
    role: str,
    avatar_url: str | None,
    default_page_id: str,
) -> dict[str, Any]:
    return {
        "userId": user_id,
        "tenantId": _DEFAULT_TENANT_ID,
        "email": email,
        "displayName": display_name,
        "role": _normalize_oracle_role(role),
        "avatarUrl": avatar_url,
        "timezone": os.getenv("ORACLE_DEFAULT_TIMEZONE", "Asia/Dubai"),
        "locale": os.getenv("ORACLE_DEFAULT_LOCALE", "en-AE"),
        "defaultPageId": default_page_id,
        "canvasPreferences": {
            "defaultDensity": "comfortable",
            "defaultPlacementMode": "append_after_last_visible_component",
            "showLineageBadges": True,
        },
        "policyProfileId": os.getenv("ORACLE_POLICY_PROFILE_ID", "policy_sales_director_standard_v4"),
        "createdAt": os.getenv("ORACLE_PROFILE_CREATED_AT", _now()),
        "updatedAt": _now(),
    }


async def _get_current_user_profile(request: Request, user: UserPrincipal) -> dict[str, Any]:
    seed_page = await canvas_service.ensure_default_page(
        tenant_id=_DEFAULT_TENANT_ID,
        owner_id=user.user_id,
        title=os.getenv("ORACLE_DEFAULT_PAGE_TITLE", "Oracle Main Canvas"),
    )
    pool = getattr(request.app.state, "db_pool", None)
    if pool is None:
        raise HTTPException(status_code=503, detail="Database unavailable.")
    async with pool.acquire() as conn:
        row = await conn.fetchrow(
            """
            SELECT
                COALESCE(full_name, split_part(email, '@', 1), id::text) AS display_name,
                COALESCE(email, id::text || '@velocity.local') AS email,
                avatar_url
            FROM users_and_roles
            WHERE id = $1::uuid
            """,
            user.user_id,
        )
    return _build_user_profile(
        user_id=user.user_id,
        email=row["email"] if row else f"{user.user_id}@velocity.local",
        display_name=row["display_name"] if row else user.user_id,
        role=user.role,
        avatar_url=row["avatar_url"] if row else None,
        default_page_id=seed_page["pageId"],
    )


async def _ctx_from_request(request: Request, user: UserPrincipal) -> PolicyContext:
    me = await _get_current_user_profile(request, user)
    return PolicyContext(
        tenant_id=me["tenantId"],
        actor_id=me["userId"],
        actor_role=me["role"],
    )


async def _resolve_page_id(request: Request, user: UserPrincipal, page_id: str) -> str:
    normalized = (page_id or "").strip()
    if normalized and normalized.lower() != "main":
        return normalized
    me = await _get_current_user_profile(request, user)
    return str(me["defaultPageId"])


# ── Pydantic Models ───────────────────────────────────────────────────────────

class PromptSubmitRequest(BaseModel):
    clientRequestId: str = Field(..., description="Client-generated idempotency key")
    branchId: str
    prompt: str = Field(..., min_length=1, max_length=4096)
    conversationContext: list[dict[str, str]] = Field(default_factory=list)
    placementMode: str = Field("append_after_last_visible_component")
    targetLeadId: str | None = None
    plannedWriteback: dict[str, Any] = Field(default_factory=dict)


class ForkCreateRequest(BaseModel):
    recipientUserId: str
    sourceRevision: int
    visibility: str = Field("private", pattern="^(private|team)$")
    message: str = ""


class RollbackRequest(BaseModel):
    targetRevision: int = Field(..., ge=1)
    clientRequestId: str


class MergeRequestCreateRequest(BaseModel):
    sourcePageId: str
    sourceBranchId: str
    targetPageId: str
    targetBranchId: str
    title: str = Field(..., min_length=1, max_length=256)
    description: str = ""


class MergeReviewRequest(BaseModel):
    decision: str = Field(..., pattern="^(approve|reject|changes_requested)$")
    comment: str = ""
    resolutions: list[dict[str, Any]] = Field(default_factory=list)


class TemplateSynthesizeRequest(BaseModel):
    prompt: str
    dataShape: list[str]
    styleSignatureRef: str | None = None


class PersonaRenderRequest(BaseModel):
    promptName: str = Field(..., pattern="^(qd_calculator|lead_tagger|cctv_profiler)$")
    variables: dict[str, Any] = Field(default_factory=dict)


class PageCreateRequest(BaseModel):
    title: str = Field(default="Untitled Canvas", max_length=256)


class PageUpdateRequest(BaseModel):
    title: str = Field(..., min_length=1, max_length=256)


# ── Endpoints ─────────────────────────────────────────────────────────────────

@router.get("/me", summary="Get current user profile")
async def get_me(request: Request, user: UserPrincipal = Depends(get_current_user)) -> dict:
    return _ok(await _get_current_user_profile(request, user))


@router.get("/canvas-pages", summary="List canvas pages for current user")
async def list_canvas_pages(
    request: Request,
    search: str | None = None,
    limit: int = 50,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    ctx = await _ctx_from_request(request, user)
    pages = await canvas_service.list_pages(
        tenant_id=ctx.tenant_id,
        owner_id=ctx.actor_id,
        search=search,
        limit=limit,
    )
    return _ok(pages, meta={"count": len(pages)})


@router.post("/canvas-pages", summary="Create a new canvas page")
async def create_canvas_page(
    payload: PageCreateRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    ctx = await _ctx_from_request(request, user)
    page = await canvas_service.create_page(
        tenant_id=ctx.tenant_id,
        owner_id=ctx.actor_id,
        title=payload.title.strip() or "Untitled Canvas",
    )
    return _ok(page)


@router.get("/canvas-pages/{page_id}", summary="Get canvas page by ID")
async def get_canvas_page(page_id: str, request: Request, user: UserPrincipal = Depends(get_current_user)) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    page = await canvas_service.get_page(page_id, ctx.tenant_id)
    if not page:
        raise HTTPException(status_code=404, detail=f"Canvas page '{page_id}' not found.")
    return _ok(page)


@router.patch("/canvas-pages/{page_id}", summary="Rename a canvas page")
async def rename_canvas_page(
    page_id: str,
    payload: PageUpdateRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    try:
        page = await canvas_service.update_page_title(
            page_id=page_id,
            tenant_id=ctx.tenant_id,
            owner_id=ctx.actor_id,
            title=payload.title,
        )
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return _ok(page)


@router.delete("/canvas-pages/{page_id}", summary="Delete a canvas page")
async def delete_canvas_page(
    page_id: str,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    try:
        await canvas_service.delete_page(
            page_id=page_id,
            tenant_id=ctx.tenant_id,
            owner_id=ctx.actor_id,
        )
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return _ok({"pageId": page_id, "deleted": True})


@router.post("/canvas-pages/{page_id}/prompts", summary="Submit a prompt to generate canvas components")
async def submit_prompt(
    page_id: str,
    payload: PromptSubmitRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    execution = await prompt_orchestrator.execute(
        tenant_id=ctx.tenant_id,
        page_id=page_id,
        branch_id=payload.branchId,
        actor_id=ctx.actor_id,
        actor_role=ctx.actor_role,
        prompt=payload.prompt,
        conversation_context=payload.conversationContext,
        client_request_id=payload.clientRequestId,
        placement_mode=payload.placementMode,
    )
    if execution["status"] == "failed":
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail={"errors": execution.get("warnings", [])},
        )
    page = await canvas_service.get_page(page_id, ctx.tenant_id)
    action = await oracle_action_service.create_from_execution(
        execution=execution,
        target_entity_type="lead" if payload.targetLeadId else "canvas_page",
        target_entity_id=payload.targetLeadId or page_id,
        action_type="oracle_prompt_writeback_plan" if payload.targetLeadId else "oracle_canvas_generation",
        writeback_payload=payload.plannedWriteback,
    )
    return _ok({
        "executionId": execution["executionId"],
        "actionId": action["actionId"],
        "status": execution["status"],
        "pageId": page_id,
        "branchId": payload.branchId,
        "headRevision": execution.get("headRevision", page.get("headRevision", 0) if page else 0),
        "componentsCreated": execution.get("componentsCreated", []),
        "summary": execution.get("summary", ""),
        "warnings": execution.get("warnings", []),
        "components": page.get("components", []) if page else [],
    })


@router.post("/canvas-pages/{page_id}/forks", summary="Create a fork (share) from a canvas page")
async def create_fork(
    page_id: str,
    payload: ForkCreateRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    page = await canvas_service.get_page(page_id, ctx.tenant_id)
    if not page:
        raise HTTPException(status_code=404, detail="Source page not found.")
    try:
        fork = await collaboration_service.create_fork(
            source_page=page,
            recipient_user_id=payload.recipientUserId,
            created_by=ctx.actor_id,
            visibility=payload.visibility,
            message=payload.message,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    return _ok(fork)


@router.post("/canvas-pages/{page_id}/rollback", summary="Rollback canvas to a prior revision")
async def rollback_canvas(
    page_id: str,
    payload: RollbackRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    result = await canvas_service.rollback(
        page_id=page_id,
        tenant_id=ctx.tenant_id,
        actor_id=ctx.actor_id,
        target_revision=payload.targetRevision,
        idempotency_key=payload.clientRequestId,
    )
    page = await canvas_service.get_page(page_id, ctx.tenant_id)
    return _ok({
        "pageId": page_id,
        "headRevision": result.get("revisionNumber", payload.targetRevision),
        "components": page.get("components", []) if page else [],
    })


@router.get("/canvas-pages/{page_id}/revisions", summary="List revision history for a canvas page")
async def list_revisions(page_id: str, request: Request, user: UserPrincipal = Depends(get_current_user)) -> dict:
    page_id = await _resolve_page_id(request, user, page_id)
    ctx = await _ctx_from_request(request, user)
    revisions = await canvas_service.list_revisions(page_id, ctx.tenant_id)
    return _ok(revisions, meta={"count": len(revisions)})


@router.get("/component-templates", summary="List component templates")
async def list_templates(
    category: str | None = None,
    status: str | None = None,
    search: str | None = None,
    limit: int = 50,
    offset: int = 0,
) -> dict:
    result = codebook_service.list_templates(
        category=category,
        status=status,
        search=search,
        limit=limit,
        offset=offset,
    )
    return _ok(result["templates"], meta={"count": result["total"], "limit": limit, "offset": offset})


@router.post("/component-templates/synthesize", summary="Synthesize a new component template from a prompt")
async def synthesize_template(
    payload: TemplateSynthesizeRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    me = await _get_current_user_profile(request, user)
    template = codebook_service.synthesize_template(
        prompt=payload.prompt,
        data_shapes=payload.dataShape,
    )
    template["tenantId"] = me["tenantId"]
    template.setdefault("createdAt", _now())
    template.setdefault("updatedAt", _now())
    return _ok(template)


@router.get("/persona/health", summary="Health check for Oracle persona prompt loading")
async def persona_health() -> dict:
    return _ok(await persona_service.health())


@router.post("/persona/render", summary="Render a subordinate Oracle persona prompt")
async def persona_render(payload: PersonaRenderRequest) -> dict:
    try:
        rendered = await persona_service.render_prompt(
            prompt_name=payload.promptName,
            variables=payload.variables,
        )
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return _ok(rendered)


@router.get("/merge-requests", summary="List merge requests for a target page")
async def list_merge_requests(targetPageId: str | None = None, status: str | None = None) -> dict:
    if not targetPageId:
        raise HTTPException(status_code=400, detail="targetPageId query param required")
    mrs = await collaboration_service.list_merge_requests(targetPageId, status)
    return _ok(mrs, meta={"count": len(mrs)})


@router.post("/merge-requests", summary="Open a merge request")
async def create_merge_request(
    payload: MergeRequestCreateRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    ctx = await _ctx_from_request(request, user)
    source_page = await canvas_service.get_page(payload.sourcePageId, ctx.tenant_id)
    target_page = await canvas_service.get_page(payload.targetPageId, ctx.tenant_id)
    if not source_page or not target_page:
        raise HTTPException(status_code=404, detail="Source or target page not found.")

    mr = await collaboration_service.open_merge_request(
        tenant_id=ctx.tenant_id,
        source_page_id=payload.sourcePageId,
        source_branch_id=payload.sourceBranchId,
        source_head_revision=source_page.get("headRevision", 0),
        target_page_id=payload.targetPageId,
        target_branch_id=payload.targetBranchId,
        target_base_revision=target_page.get("headRevision", 0),
        title=payload.title,
        description=payload.description,
        created_by=ctx.actor_id,
        source_components=source_page.get("components", []),
        target_components=target_page.get("components", []),
        base_components=[],  # Simplified: empty base for demo
    )
    return _ok(mr)


@router.post("/merge-requests/{mr_id}/review", summary="Submit a merge request review")
async def review_merge_request(
    mr_id: str,
    payload: MergeReviewRequest,
    request: Request,
    user: UserPrincipal = Depends(get_current_user),
) -> dict:
    ctx = await _ctx_from_request(request, user)
    mr = await collaboration_service.review_merge_request(
        mr_id=mr_id,
        decision=payload.decision,
        reviewer_id=ctx.actor_id,
        comment=payload.comment,
        resolutions=payload.resolutions,
    )
    return _ok(mr)


# ── WebSocket ─────────────────────────────────────────────────────────────────

class OracleConnectionManager:
    def __init__(self) -> None:
        self.active: dict[str, Set[WebSocket]] = {}

    async def connect(self, ws: WebSocket, page_id: str) -> None:
        await ws.accept()
        self.active.setdefault(page_id, set()).add(ws)

    def disconnect(self, ws: WebSocket, page_id: str) -> None:
        page_connections = self.active.get(page_id, set())
        page_connections.discard(ws)

    async def broadcast_page(self, page_id: str, payload: dict) -> None:
        dead: set[WebSocket] = set()
        for ws in self.active.get(page_id, set()):
            try:
                await ws.send_text(json.dumps(payload))
            except Exception:
                dead.add(ws)
        if dead:
            self.active.get(page_id, set()).difference_update(dead)


oracle_manager = OracleConnectionManager()


@router.websocket("/ws/oracle/canvas/{page_id}")
async def oracle_canvas_ws(ws: WebSocket, page_id: str) -> None:
    """
    WebSocket endpoint for real-time Oracle canvas collaboration.
    Event types: oracle.page.revision.committed, oracle.prompt.received, oracle.presence.updated
    """
    await oracle_manager.connect(ws, page_id)
    try:
        while True:
            data = await ws.receive_text()
            try:
                msg = json.loads(data)
                # Reflect heartbeat
                if msg.get("type") == "heartbeat":
                    await ws.send_text(json.dumps({"type": "heartbeat.ack", "timestamp": _now()}))
            except json.JSONDecodeError:
                pass
    except WebSocketDisconnect:
        oracle_manager.disconnect(ws, page_id)


# ── Pre-made templates seed ───────────────────────────────────────────────────

