from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path

from terminaluse.core import ApiError
from terminaluse.lib.cli.handlers.filesystem_handlers import create_filesystem, push_directory
from terminaluse.lib.client import TerminalUse
from terminaluse.lib.sdk.config.agent_manifest import AgentManifest

BUILDER_ARTIFACT_PROJECT_NAME = "TerminalUse Builder Artifacts"
BUILDER_ARTIFACT_PROJECT_OWNERSHIP_MARKER = "terminaluse-builder-artifacts:v1"
_MAX_PROJECT_LIST_LIMIT = 100


@dataclass(frozen=True)
class DeploySourceSnapshot:
    filesystem_id: str
    project_id: str
    filesystem_name: str


def _slugify_segment(value: str) -> str:
    normalized = value.strip().lower().replace("/", "-").replace("_", "-")
    normalized = "".join(ch if ch.isalnum() or ch == "-" else "-" for ch in normalized)
    normalized = "-".join(filter(None, normalized.split("-")))
    return normalized[:48] or "agent"


def _build_artifact_project_description(namespace_id: str) -> str:
    return (
        "Managed immutable Builder deploy artifacts. "
        f"[{BUILDER_ARTIFACT_PROJECT_OWNERSHIP_MARKER};namespace={namespace_id}]"
    )


def _is_builder_artifact_project(project: object, namespace_id: str) -> bool:
    description = getattr(project, "description", None) or ""
    project_namespace_id = getattr(project, "namespace_id", None)
    return (
        BUILDER_ARTIFACT_PROJECT_OWNERSHIP_MARKER in description
        and f"namespace={namespace_id}" in description
        and (project_namespace_id is None or project_namespace_id == namespace_id)
    )


def _build_builder_artifact_name(target_agent_name: str) -> str:
    timestamp = datetime.now(UTC).isoformat().replace(":", "-").replace(".", "-")
    return f"builder-artifact-{_slugify_segment(target_agent_name)}-{timestamp}"


def _iter_namespace_projects(client: TerminalUse, namespace_id: str):
    page_number = 1
    while True:
        projects = list(
            client.projects.list(
                namespace_id=namespace_id,
                limit=_MAX_PROJECT_LIST_LIMIT,
                page_number=page_number,
            )
        )
        if not projects:
            return

        yield from projects

        if len(projects) < _MAX_PROJECT_LIST_LIMIT:
            return
        page_number += 1


def _extract_api_error_message(error: ApiError) -> str:
    body = error.body
    if isinstance(body, dict):
        message = body.get("message") or body.get("detail")
        if isinstance(message, str):
            return message
    if isinstance(body, str):
        return body
    return str(error)


def _is_duplicate_project_create_error(error: ApiError) -> bool:
    if error.status_code not in {400, 409}:
        return False
    return "already exists" in _extract_api_error_message(error).lower()


def get_or_create_builder_artifact_project(client: TerminalUse, namespace_id: str) -> str:
    candidates = []
    for project in _iter_namespace_projects(client, namespace_id):
        if getattr(project, "name", None) != BUILDER_ARTIFACT_PROJECT_NAME:
            continue
        detailed = client.projects.retrieve(project.id)
        if _is_builder_artifact_project(detailed, namespace_id):
            candidates.append(detailed)

    if candidates:
        candidates.sort(
            key=lambda project: getattr(project, "created_at", None) or "1970-01-01T00:00:00Z",
            reverse=True,
        )
        return candidates[0].id

    try:
        created = client.projects.create(
            namespace_id=namespace_id,
            name=BUILDER_ARTIFACT_PROJECT_NAME,
            description=_build_artifact_project_description(namespace_id),
        )
        return created.id
    except ApiError as error:
        if not _is_duplicate_project_create_error(error):
            raise

    for project in _iter_namespace_projects(client, namespace_id):
        if getattr(project, "name", None) != BUILDER_ARTIFACT_PROJECT_NAME:
            continue
        detailed = client.projects.retrieve(project.id)
        if _is_builder_artifact_project(detailed, namespace_id):
            return detailed.id

    raise RuntimeError(
        "Builder artifact project creation reported a duplicate, but no managed artifact project could be found."
    )


def capture_deploy_source_snapshot(
    *,
    client: TerminalUse,
    namespace_id: str,
    target_agent_name: str,
    config_path: str,
) -> DeploySourceSnapshot:
    manifest = AgentManifest.from_yaml(file_path=config_path)
    build_context_root = (Path(config_path).parent / manifest.build.context.root).resolve()

    project_id = get_or_create_builder_artifact_project(client, namespace_id)
    filesystem_name = _build_builder_artifact_name(target_agent_name)
    filesystem = create_filesystem(client, project_id, filesystem_name)

    with manifest.context_manager(build_context_root) as build_ctx:
        assert build_ctx.path is not None
        push_directory(client, filesystem.id, Path(build_ctx.path))

    return DeploySourceSnapshot(
        filesystem_id=filesystem.id,
        project_id=project_id,
        filesystem_name=filesystem_name,
    )
