"""gitea — beyond-parity: admin API CRUD lifecycle (phase gtea).

Proves the gitea admin REST API: user + org + token creation, read-back, and deletion.
Non-vacuous: a misconfigured gitea (broken DB, broken API routing) fails any of these.
"""

from __future__ import annotations

import base64
import json
import os
import secrets
import sys
import urllib.error
import urllib.request

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner"))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import ssl

from ops import admin_creds  # noqa: E402

_CTX = ssl.create_default_context()
_CTX.check_hostname = False
_CTX.verify_mode = ssl.CERT_NONE


def _api(domain, path, method="GET", body=None, user="", password="", token=""):
    data = json.dumps(body).encode() if body is not None else None
    if token:
        headers = {"Authorization": f"token {token}"}
    else:
        auth = base64.b64encode(f"{user}:{password}".encode()).decode()
        headers = {"Authorization": f"Basic {auth}"}
    if data:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(
        f"https://{domain}/api/v1{path}", data=data, headers=headers, method=method
    )
    try:
        with urllib.request.urlopen(req, timeout=20, context=_CTX) as r:
            raw = r.read()
            return r.status, (json.loads(raw) if raw else {})
    except urllib.error.HTTPError as e:
        raw = e.read()
        try:
            return e.code, json.loads(raw)
        except (ValueError, json.JSONDecodeError):
            return e.code, {}


def test_admin_api_user_org_token_lifecycle(live_app):
    """Admin API CRUD: create user → create org → create API token → read-back → delete.

    Proves the full admin-API lifecycle:
      1. Create a test user (admin-creates-user API)
      2. Create an org (admin-creates-org API)
      3. Create an API token for the test user (user-generates-token API)
      4. Read back the user + org via the token
      5. Delete the token, org, and user (cleanup)
    """
    adm_user, adm_pass = admin_creds(live_app)
    suffix = secrets.token_hex(4)
    test_user = f"testuser-{suffix}"
    test_org = f"testorg-{suffix}"
    token_name = f"ci-test-token-{suffix}"
    test_pass = secrets.token_hex(12) + "A1"  # at least one uppercase + digit for any policy

    # 1. Create test user
    status, body = _api(
        live_app,
        "/admin/users",
        method="POST",
        body={
            "username": test_user,
            "email": f"{test_user}@ci.local",
            "password": test_pass,
            "must_change_password": False,
            "login_name": test_user,
            "source_id": 0,
        },
        user=adm_user,
        password=adm_pass,
    )
    assert status == 201, f"user create HTTP {status}: {body}"
    assert body.get("login") == test_user, f"unexpected login: {body}"

    try:
        # 2. Create org (as admin, add test user as member)
        status, body = _api(
            live_app,
            "/orgs",
            method="POST",
            body={"username": test_org, "visibility": "public"},
            user=adm_user,
            password=adm_pass,
        )
        assert status == 201, f"org create HTTP {status}: {body}"
        assert body.get("username") == test_org, f"unexpected org: {body}"

        try:
            # 3. Create API token for test user (admin creates token on behalf of user).
            # Gitea 1.22+ requires explicit scopes; supply the minimum needed for steps 4-5.
            status, tok_body = _api(
                live_app,
                f"/users/{test_user}/tokens",
                method="POST",
                body={"name": token_name, "scopes": ["read:user", "read:organization"]},
                user=adm_user,
                password=adm_pass,
            )
            assert status == 201, f"token create HTTP {status}: {tok_body}"
            token = tok_body.get("sha1")
            assert token, f"token sha1 missing from response: {tok_body}"

            # 4. Read back via token: authenticated call as test_user
            status, me = _api(live_app, "/user", token=token)
            assert status == 200, f"GET /user with token HTTP {status}"
            assert me.get("login") == test_user, f"token authenticated as wrong user: {me}"

            # 5. Read org via token
            status, org = _api(live_app, f"/orgs/{test_org}", token=token)
            assert status == 200, f"GET /orgs/{test_org} HTTP {status}: {org}"
            assert org.get("username") == test_org

            # 6. Delete the token
            status, _ = _api(
                live_app,
                f"/users/{test_user}/tokens/{token_name}",
                method="DELETE",
                user=adm_user,
                password=adm_pass,
            )
            assert status in (204, 404), f"token delete HTTP {status}"

        finally:
            # Delete org
            _api(live_app, f"/orgs/{test_org}", method="DELETE", user=adm_user, password=adm_pass)

    finally:
        # Delete test user (admin only)
        _api(
            live_app, f"/admin/users/{test_user}", method="DELETE", user=adm_user, password=adm_pass
        )
