# retoor <retoor@molodetz.nl>

import logging

import httpx

from devplacepy import stealth
from devplacepy.config import INTERNAL_GATEWAY_URL
from devplacepy.services.gitea.config import HTTP_TIMEOUT_SECONDS, GiteaConfig

logger = logging.getLogger(__name__)

MAX_TOKENS = 3000
MAX_ISSUES = 50
BODY_EXCERPT_MAX = 600
PLAN_MAX = 80000
UNGROUPED_LABEL = "General"

SYSTEM_PROMPT = (
    "You are a senior engineering lead for DevPlace, a social network for software "
    "developers. You are given the full list of OPEN issue tickets from a Gitea tracker. "
    "Produce a single, clear delivery plan in GitHub-flavored markdown that GROUPS the "
    "tickets into logical workstreams and ORDERS them so dependencies and high-impact work "
    "come first. Never invent tickets, numbers, or facts that are not in the input.\n\n"
    "Return ONLY markdown, using EXACTLY this structure:\n"
    "# Open Tickets Planning\n"
    "A short paragraph summarising the overall plan and how many tickets it covers.\n\n"
    "Then one level-2 heading per group (a workstream or theme). Under each group, an "
    "ordered (numbered) list of its tickets. Each list item MUST start with the ticket "
    "number as '#N', then the title, then a single sentence explaining the chosen order "
    "or the work to do, for example:\n"
    "1. #12 Fix the login redirect - blocks every authenticated flow, do first.\n\n"
    "End with a level-2 heading '## Suggested Order' giving a flat numbered list of every "
    "ticket number in the recommended execution order."
)


def _excerpt(text: str) -> str:
    text = (text or "").strip().replace("\r\n", "\n")
    if len(text) > BODY_EXCERPT_MAX:
        return text[:BODY_EXCERPT_MAX].rstrip() + "..."
    return text


def _labels(issue: dict) -> list[str]:
    labels = issue.get("labels") or []
    names: list[str] = []
    for label in labels:
        if isinstance(label, dict):
            name = str(label.get("name", "")).strip()
        else:
            name = str(label).strip()
        if name:
            names.append(name)
    return names


def _group_key(issue: dict) -> str:
    labels = _labels(issue)
    return labels[0] if labels else UNGROUPED_LABEL


def _fallback(issues: list[dict]) -> str:
    groups: dict[str, list[dict]] = {}
    for issue in issues:
        groups.setdefault(_group_key(issue), []).append(issue)

    ordered_group_names = sorted(
        groups,
        key=lambda name: (name == UNGROUPED_LABEL, name.lower()),
    )

    lines: list[str] = ["# Open Tickets Planning", ""]
    lines.append(
        f"Deterministic plan covering {len(issues)} open ticket"
        f"{'' if len(issues) == 1 else 's'}, grouped by primary label and ordered by "
        "ticket number within each group."
    )
    lines.append("")

    flat_order: list[int] = []
    for name in ordered_group_names:
        members = sorted(groups[name], key=lambda i: int(i.get("number", 0)))
        lines.append(f"## {name}")
        for position, issue in enumerate(members, start=1):
            number = int(issue.get("number", 0))
            title = str(issue.get("title", "")).strip() or "Untitled"
            excerpt = _excerpt(issue.get("body", ""))
            rationale = excerpt or "No description provided."
            lines.append(f"{position}. #{number} {title} - {rationale}")
            flat_order.append(number)
        lines.append("")

    lines.append("## Suggested Order")
    for position, number in enumerate(flat_order, start=1):
        lines.append(f"{position}. #{number}")
    lines.append("")

    return "\n".join(lines)[:PLAN_MAX]


def _user_message(issues: list[dict]) -> str:
    parts: list[str] = ["Open tickets to plan:", ""]
    for issue in issues:
        number = int(issue.get("number", 0))
        title = str(issue.get("title", "")).strip() or "Untitled"
        labels = ", ".join(_labels(issue)) or "none"
        excerpt = _excerpt(issue.get("body", ""))
        parts.append(f"#{number} {title}")
        parts.append(f"labels: {labels}")
        parts.append(f"description: {excerpt or 'none'}")
        parts.append("")
    return "\n".join(parts)


async def generate_plan(
    issues: list[dict], config: GiteaConfig
) -> tuple[str, bool]:
    issues = list(issues)[:MAX_ISSUES]
    if not issues:
        return "# Open Tickets Planning\n\nThere are no open tickets to plan.\n", False
    if not config.ai_enhance:
        return _fallback(issues), False

    payload = {
        "model": config.ai_model,
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": _user_message(issues)},
        ],
        "max_tokens": MAX_TOKENS,
        "temperature": 0.2,
    }
    headers = {"Content-Type": "application/json"}
    if config.ai_key:
        headers["Authorization"] = f"Bearer {config.ai_key}"

    try:
        async with stealth.stealth_async_client(timeout=HTTP_TIMEOUT_SECONDS) as client:
            response = await client.post(
                INTERNAL_GATEWAY_URL, json=payload, headers=headers
            )
            response.raise_for_status()
            content = (
                response.json()
                .get("choices", [{}])[0]
                .get("message", {})
                .get("content", "")
            )
    except (httpx.HTTPError, ValueError, KeyError, IndexError) as exc:
        logger.warning("Planning generation failed, using fallback: %s", exc)
        return _fallback(issues), False

    markdown = (content or "").strip()
    if not markdown:
        logger.warning("Planning generation returned empty content, using fallback")
        return _fallback(issues), False
    logger.info("Generated AI planning for %d open tickets", len(issues))
    return markdown[:PLAN_MAX], True
