"""
ABOS AI Worker – Project Plan Generator Router
================================================
Generates a full project plan (the project itself plus a nested Feature/PBI/Task/Bug/Spike or
Milestone/Activity/Deliverable or Goal/ToDo/ActionItem tree) from a plain-language brief via
OpenRouter. Backend-facing only — called by Spring Boot's ProjectPlanAiController, which
resolves the tenant's stored OpenRouter key before forwarding the request here.

This is also the reference implementation of the schema an EXTERNAL agent must produce for
the "paste a plan" import path on the frontend — see ProjectPlanResult in schemas.py and the
copyable prompt template shown in ProjectPlanDialog.tsx.
"""

from __future__ import annotations

import logging

from fastapi import APIRouter, HTTPException, status
from pydantic import ValidationError

from app.llm_client import call_openrouter
from app.schemas import (
    ConvertProjectPlanRequest,
    GenerateProjectPlanRequest,
    GenerateProjectPlanResponse,
    ProjectPlanResult,
)

logger = logging.getLogger(__name__)

project_plan_router = APIRouter()

# Mirrors abos-web's HIERARCHY/rootTypeFor exactly (types/index.ts) — restated here so the
# prompt teaches the model valid nesting up front, rather than relying on Spring Boot's
# WorkitemService.validateHierarchy to reject an invalid tree after the fact.
_ROOT_TYPE_BY_PROJECT_TYPE = {
    "Technical": "Feature",
    "Business": "Milestone",
    "Ongoing Operations": "Goal",
}
_HIERARCHY_RULES = (
    "Feature -> children must be PBI only\n"
    "PBI -> children must be Task, Bug, or Spike\n"
    "Milestone -> children must be Activity or Deliverable\n"
    "Goal -> children must be ToDo or ActionItem\n"
    "Task, Bug, Spike, Activity, Deliverable, ToDo, and ActionItem are leaf types — no children."
)

_SCHEMA_DESCRIPTION = """Respond with ONLY a JSON object of this exact shape (no markdown fences, no prose):
{
  "project": {
    "name": "string", "type": "Technical|Business|Ongoing Operations",
    "description": "string", "priority": "Low|Medium|High|Critical",
    "startDate": "YYYY-MM-DD or null", "endDate": "YYYY-MM-DD or null"
  },
  "items": [
    {
      "title": "string",
      "type": "Feature|PBI|Task|Bug|Spike|Milestone|Activity|Deliverable|Goal|ToDo|ActionItem",
      "description": "string", "priority": "Low|Medium|High|Critical",
      "estimateHours": number or null, "dueDate": "YYYY-MM-DD or null",
      "children": [ /* same shape, recursively, per the nesting rules below */ ]
    }
  ]
}"""


def build_system_message() -> str:
    """Exposed (not prefixed with _) so ProjectPlanDialog.tsx's copyable external-agent prompt
    and this service's actual LLM call stay in sync from one source of truth — Spring Boot's
    ProjectPlanAiController can fetch this verbatim via a small passthrough if the frontend
    ever needs the live text rather than a hand-copied version."""
    return (
        "You are a precise project-planning assistant embedded in a project management tool. "
        "Given a project type and a brief, produce a complete, realistic project plan as a "
        "nested JSON tree.\n\n"
        f"{_SCHEMA_DESCRIPTION}\n\n"
        "Hierarchy rules (violating these produces an invalid plan):\n"
        f"{_HIERARCHY_RULES}\n\n"
        "Root-level items in \"items\" must ALL be the type required for the given project type: "
        "Technical -> Feature, Business -> Milestone, Ongoing Operations -> Goal.\n"
        "Produce a genuinely useful plan: 3-8 root items, each with a sensible number of "
        "children given the brief's scope. Leave estimateHours/dueDate null if you have no "
        "reasonable basis to guess them — do not invent precise numbers."
    )


def _build_user_message(request: GenerateProjectPlanRequest) -> str:
    required_root = _ROOT_TYPE_BY_PROJECT_TYPE.get(request.project_type, "Feature")
    return (
        f"Project type: {request.project_type} (root items must all be type \"{required_root}\")\n"
        f"Brief: {request.brief}"
    )


def build_conversion_system_message() -> str:
    """The "paste a plan" path's system prompt. Unlike build_system_message (which invents a
    plan from a brief), this instructs the model to faithfully TRANSCRIBE whatever plan the user
    already pasted — preserve their titles/wording/structure, only inferring the type/nesting
    each item must have to satisfy the hierarchy rules. Exposed (not prefixed with _) for the
    same reason as build_system_message — a single source of truth if ever surfaced verbatim."""
    return (
        "You convert a user-supplied, free-text project plan (an outline, bullet list, notes, "
        "or prose — any format) into a structured JSON tree. Do NOT invent new scope, tasks, or "
        "details that aren't implied by the pasted text — this is a format conversion, not a "
        "brainstorm. Preserve the user's own titles and wording as closely as possible.\n\n"
        f"{_SCHEMA_DESCRIPTION}\n\n"
        "Hierarchy rules (violating these produces an invalid plan):\n"
        f"{_HIERARCHY_RULES}\n\n"
        "Root-level items in \"items\" must ALL be the type required for the given project type: "
        "Technical -> Feature, Business -> Milestone, Ongoing Operations -> Goal. If the pasted "
        "text's structure doesn't cleanly map to these levels, use your best judgment to slot each "
        "item into the closest valid level rather than dropping it. Leave estimateHours/dueDate "
        "null unless the pasted text actually specifies them — do not invent precise numbers."
    )


def _build_conversion_user_message(request: ConvertProjectPlanRequest) -> str:
    required_root = _ROOT_TYPE_BY_PROJECT_TYPE.get(request.project_type, "Feature")
    return (
        f"Project type: {request.project_type} (root items must all be type \"{required_root}\")\n"
        f"Plan text to convert:\n{request.plan_text}"
    )


async def _call_and_validate(openrouter_key: str, model: str | None, system_message: str, user_message: str) -> GenerateProjectPlanResponse:
    try:
        # call_openrouter's own framing ("data extraction assistant") is generic enough to work
        # here too — `prompt` carries the real instructions (schema + hierarchy rules), and
        # `document_text` carries the brief/pasted text. Reusing it rather than hand-rolling a
        # second HTTP call for what is mechanically the same JSON-mode request/response/error
        # handling.
        data, model_used = await call_openrouter(
            openrouter_key=openrouter_key,
            prompt=system_message,
            document_text=user_message,
            model=model,
        )
    except Exception as exc:
        logger.error("Project plan generation failed: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Project plan generation failed: {exc}",
        ) from exc

    try:
        plan = ProjectPlanResult.model_validate(data)
    except ValidationError as exc:
        logger.error("LLM produced a plan that doesn't match the expected schema: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"The AI's plan didn't match the expected schema: {exc}",
        ) from exc

    return GenerateProjectPlanResponse(plan=plan, model_used=model_used)


@project_plan_router.post(
    "/generate-project-plan",
    response_model=GenerateProjectPlanResponse,
    status_code=status.HTTP_200_OK,
    summary="Generate a full project plan (project + task/milestone/goal tree) via OpenRouter",
    tags=["Backend-Facing"],
)
async def generate_project_plan(request: GenerateProjectPlanRequest) -> GenerateProjectPlanResponse:
    if request.project_type not in _ROOT_TYPE_BY_PROJECT_TYPE:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"Unknown projectType '{request.project_type}'. Expected one of: "
                   f"{list(_ROOT_TYPE_BY_PROJECT_TYPE)}",
        )
    return await _call_and_validate(
        request.openrouter_key, request.model, build_system_message(), _build_user_message(request),
    )


@project_plan_router.post(
    "/convert-project-plan",
    response_model=GenerateProjectPlanResponse,
    status_code=status.HTTP_200_OK,
    summary="Convert a pasted free-text project plan into the structured JSON tree via OpenRouter",
    tags=["Backend-Facing"],
)
async def convert_project_plan(request: ConvertProjectPlanRequest) -> GenerateProjectPlanResponse:
    if request.project_type not in _ROOT_TYPE_BY_PROJECT_TYPE:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"Unknown projectType '{request.project_type}'. Expected one of: "
                   f"{list(_ROOT_TYPE_BY_PROJECT_TYPE)}",
        )
    if not request.plan_text.strip():
        raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="planText is required")
    return await _call_and_validate(
        request.openrouter_key, request.model, build_conversion_system_message(), _build_conversion_user_message(request),
    )
