#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import re
import sys
import uuid
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


def request_json(
    method: str,
    base_url: str,
    path: str,
    *,
    payload: dict[str, Any] | None = None,
    timeout: float = 5.0,
) -> tuple[int, dict[str, Any] | str, bool]:
    body = json.dumps(payload).encode("utf-8") if payload is not None else None
    req = Request(
        f"{base_url}{path}",
        data=body,
        method=method,
        headers={"Content-Type": "application/json"},
    )
    try:
        with urlopen(req, timeout=timeout) as response:
            raw = response.read().decode("utf-8")
            if not raw:
                return response.status, {}, False
            return response.status, json.loads(raw), False
    except HTTPError as exc:
        raw = exc.read().decode("utf-8")
        return exc.code, (json.loads(raw) if raw else {}), True
    except URLError as exc:
        return 0, str(exc), True


def assert_has_trace(payload: dict[str, Any]) -> None:
    trace = payload.get("trace_id")
    assert isinstance(trace, str), "trace_id is required"
    assert re.fullmatch(r"[0-9a-f]{32}", trace), "trace_id format is invalid"


def unwrap(payload: dict[str, Any], wrapper: str) -> dict[str, Any]:
    wrapped = payload.get(wrapper)
    if isinstance(wrapped, dict):
        return wrapped
    return payload


def main() -> int:
    parser = argparse.ArgumentParser(description="Smoke test for 1C agent API.")
    parser.add_argument(
        "--base-url",
        default="http://docker-test.cin.su:8090",
        help="Base API URL",
    )
    parser.add_argument(
        "--timeout",
        type=float,
        default=5.0,
        help="Request timeout in seconds",
    )
    parser.add_argument(
        "--with-turn-check",
        action="store_true",
        help="Run /turn check (may be slower on first warm-up).",
    )
    parser.add_argument(
        "--report",
        type=Path,
        help="Write JSON smoke report to file.",
    )
    args = parser.parse_args()

    steps: list[dict[str, Any]] = []
    project_id: str | None = None
    chat_id: str | None = None
    passed = 0
    failed: list[dict[str, str]] = []

    try:
        status, health, is_error = request_json("GET", args.base_url, "/v1/health", timeout=args.timeout)
        step = {"name": "health", "status": status, "error": is_error}
        if not is_error:
            assert status == 200 and health["status"] == "ok"
            assert_has_trace(health)
            step["service"] = health.get("service")
        else:
            raise AssertionError(f"Health request failed: {health}")
        steps.append(step)
        passed += 1

        status, state, is_error = request_json("GET", args.base_url, "/v1/state", timeout=args.timeout)
        step = {"name": "state", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"State request failed: {state}")
        assert status == 200
        assert state.get("state", {}).get("projects", 0) >= 0
        assert_has_trace(state)
        step["project_count"] = state["state"]["projects"]
        steps.append(step)
        passed += 1

        project_name = f"smoke-{uuid.uuid4()}"
        status, project, is_error = request_json(
            "POST",
            args.base_url,
            "/v1/projects",
            payload={"name": project_name, "description": "agent smoke"},
            timeout=args.timeout,
        )
        step = {"name": "create_project", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"Create project failed: {project}")
        assert status == 201 and "project" in project
        project_id = project["project"]["id"]
        assert_has_trace(project)
        steps.append(step)
        passed += 1

        status, project_check, is_error = request_json(
            "GET",
            args.base_url,
            f"/v1/projects/{project_id}",
            timeout=args.timeout,
        )
        step = {"name": "get_project", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"Get project failed: {project_check}")
        assert status == 200
        assert unwrap(project_check, "project")["id"] == project_id
        assert_has_trace(project_check)
        steps.append(step)
        passed += 1

        status, chat, is_error = request_json(
            "POST",
            args.base_url,
            f"/v1/projects/{project_id}/chats",
            payload={"title": "Диалог для smoke", "rag_profile": "default", "temperature": 0.12, "max_tokens": 128},
            timeout=args.timeout,
        )
        step = {"name": "create_chat", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"Create chat failed: {chat}")
        assert status == 201 and "chat" in chat
        chat_id = chat["chat"]["id"]
        assert_has_trace(chat)
        steps.append(step)
        passed += 1

        status, message, is_error = request_json(
            "POST",
            args.base_url,
            f"/v1/projects/{project_id}/chats/{chat_id}/messages",
            payload={"role": "user", "content": "Тестовое сообщение smoke"},
            timeout=args.timeout,
        )
        step = {"name": "add_message", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"Add message failed: {message}")
        assert status == 201 and "message" in message
        assert message["message"]["content"] == "Тестовое сообщение smoke"
        assert_has_trace(message)
        steps.append(step)
        passed += 1

        status, listed, is_error = request_json(
            "GET",
            args.base_url,
            f"/v1/projects/{project_id}/chats/{chat_id}/messages?limit=10",
            timeout=args.timeout,
        )
        step = {"name": "list_messages", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"List messages failed: {listed}")
        assert status == 200
        assert len(listed.get("messages", [])) == 1
        assert_has_trace(listed)
        steps.append(step)
        passed += 1

        status, state, is_error = request_json(
            "GET",
            args.base_url,
            f"/v1/projects/{project_id}/chats/{chat_id}",
            timeout=args.timeout,
        )
        step = {"name": "get_chat", "status": status, "error": is_error}
        if is_error:
            raise AssertionError(f"Get chat failed: {state}")
        assert status == 200
        chat_payload = unwrap(state, "chat")
        assert chat_payload["id"] == chat_id
        assert_has_trace(state)
        steps.append(step)
        passed += 1

        status, not_found, is_error = request_json(
            "POST",
            args.base_url,
            "/v1/projects/does-not-exist/chats",
            payload={"title": "fail-fast"},
            timeout=args.timeout,
        )
        step = {"name": "negative_not_found_chat", "status": status, "error": is_error}
        if not is_error or status != 404:
            raise AssertionError(f"Expected 404 for unknown project, got {status} {not_found}")
        assert_has_trace(not_found)
        steps.append(step)
        passed += 1

        if args.with_turn_check:
            status, turn, is_error = request_json(
                "POST",
                args.base_url,
                f"/v1/projects/{project_id}/chats/{chat_id}/turn",
                payload={"message": "Коротко: проверка маршрутизации.", "use_rag": False},
                timeout=max(args.timeout, 60.0),
            )
            step = {"name": "turn", "status": status, "error": is_error}
            if is_error:
                raise AssertionError(f"Turn failed: {turn}")
            assert status == 200
            assert_has_trace(turn)
            steps.append(step)
            passed += 1

        result = {"status": "ok", "passed": passed, "steps": steps}
        if args.report:
            args.report.parent.mkdir(parents=True, exist_ok=True)
            args.report.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        print(json.dumps(result, ensure_ascii=False))
        return 0
    except Exception as exc:
        failed.append({"error": str(exc), "at": steps[-1]["name"] if steps else "start"})
        result = {"status": "failed", "passed": passed, "steps": steps, "errors": failed}
        if args.report:
            args.report.parent.mkdir(parents=True, exist_ok=True)
            args.report.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        print(json.dumps(result, ensure_ascii=False), file=sys.stderr)
        return 1
    finally:
        if project_id is not None:
            request_json("DELETE", args.base_url, f"/v1/projects/{project_id}", timeout=args.timeout)


if __name__ == "__main__":
    raise SystemExit(main())
