"""
Brynq Identity — Local user identity management.

Handles anonymous identities (auto-created on first run) and
signed-up users (email/password/username). The identity file
at ~/.brynq/identity.json is the single source of truth.

Flow:
    First run  → ensure_identity() → anonymous brynq_xxxx identity + cloud JWT
    /signup    → signup(username, email, password) → upgrades to named account
    /login     → login(email, password) → loads existing account on new machine
    /logout    → logout() → clears JWT, keeps anonymous identity
"""

from __future__ import annotations

import json
import logging
import os
import secrets
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any

logger = logging.getLogger("brynq.identity")

_DEFAULT_DIR = Path.home() / ".brynq"


class Identity:
    """Manages local user identity.

    On first run, generates an anonymous identity (brynq_<8hex>).
    On signup, upgrades to a named account with email/username.
    On login, loads an existing account from the cloud.

    All state persists to ~/.brynq/identity.json.
    JWT token stored separately at ~/.brynq/cloud_token.
    """

    def __init__(self, data_dir: Path | None = None, cloud_url: str = "https://api.brynq.ai"):
        self._dir = data_dir or _DEFAULT_DIR
        self._path = self._dir / "identity.json"
        self._token_path = self._dir / "cloud_token"
        self._cloud_url = cloud_url.rstrip("/")
        self._data: dict[str, Any] = {}
        self._token: str | None = None
        self._load()

    # ── Properties ────────────────────────────────────────────────

    @property
    def is_anonymous(self) -> bool:
        """True if the user has NOT signed up (still anonymous)."""
        return not self._data.get("signed_up", False)

    @property
    def is_signed_up(self) -> bool:
        """True if the user has a real account with email/username."""
        return self._data.get("signed_up", False)

    @property
    def display_name(self) -> str:
        """Username if signed up, anonymous name if not."""
        if self.is_signed_up:
            return self._data.get("username", self._data.get("anonymous_name", "User"))
        return self._data.get("anonymous_name", "User")

    @property
    def user_id(self) -> str:
        """Cloud user ID (set after registration or login)."""
        return self._data.get("user_id", "")

    @property
    def email(self) -> str:
        """User's email (empty if anonymous)."""
        return self._data.get("email", "")

    @property
    def anonymous_name(self) -> str:
        """The auto-generated anonymous name (always available)."""
        return self._data.get("anonymous_name", "")

    @property
    def token(self) -> str | None:
        """Current JWT token for cloud API calls."""
        return self._token

    @property
    def data(self) -> dict[str, Any]:
        """Raw identity data (read-only copy)."""
        return dict(self._data)

    # ── Core Operations ───────────────────────────────────────────

    def ensure_identity(self) -> dict[str, Any]:
        """Ensure a local identity exists. Creates one if needed.

        On first run:
            1. Generates brynq_<8hex> anonymous name
            2. Auto-registers with cloud to get a JWT
            3. Saves identity.json

        Returns:
            The identity data dict.
        """
        if self._data.get("id"):
            # Already have an identity, just make sure we have a token
            if not self._token:
                self._load_token()
            if not self._token:
                self._auto_register()
            return dict(self._data)

        # First run — create anonymous identity
        anon_name = f"brynq_{secrets.token_hex(4)}"
        self._data = {
            "id": secrets.token_hex(16),
            "anonymous_name": anon_name,
            "created_at": time.time(),
            "signed_up": False,
        }
        self._save()
        logger.info("Created anonymous identity: %s", anon_name)

        # Auto-register with cloud
        self._auto_register()

        return dict(self._data)

    def signup(self, username: str, email: str, password: str) -> dict[str, Any]:
        """Sign up for a real account. Upgrades anonymous identity.

        Args:
            username: Desired display name.
            email: Email address.
            password: Password (min 6 chars).

        Returns:
            Updated identity data dict.

        Raises:
            ValueError: If signup fails (bad credentials, email taken, etc.)
        """
        if not username or not username.strip():
            raise ValueError("Username is required")
        if not email or "@" not in email:
            raise ValueError("Valid email is required")
        if not password or len(password) < 6:
            raise ValueError("Password must be at least 6 characters")

        username = username.strip()
        email = email.strip().lower()

        # Call cloud signup endpoint
        payload = json.dumps({
            "name": username,
            "email": email,
            "password": password,
            "turnstile_token": "",  # CAPTCHA optional for CLI
            "device_fingerprint": "",
        }).encode("utf-8")

        try:
            req = urllib.request.Request(
                f"{self._cloud_url}/auth/register",
                data=payload,
                headers={"Content-Type": "application/json"},
            )
            with urllib.request.urlopen(req, timeout=15) as resp:
                data = json.loads(resp.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            body = e.read().decode("utf-8", errors="replace")
            try:
                detail = json.loads(body).get("detail", body)
            except (json.JSONDecodeError, AttributeError):
                detail = body
            raise ValueError(f"Signup failed: {detail}")
        except urllib.error.URLError as e:
            raise ValueError(f"Cannot reach cloud server: {e.reason}")

        user_info = data.get("user", {})
        cloud_user_id = user_info.get("id", "")

        # Now login to get a JWT
        login_payload = json.dumps({
            "email": email,
            "password": password,
        }).encode("utf-8")

        try:
            login_req = urllib.request.Request(
                f"{self._cloud_url}/auth/login",
                data=login_payload,
                headers={"Content-Type": "application/json"},
            )
            with urllib.request.urlopen(login_req, timeout=15) as resp:
                login_data = json.loads(resp.read().decode("utf-8"))
        except Exception as e:
            # Signup succeeded but login failed — still save the account info
            logger.warning("Auto-login after signup failed: %s", e)
            login_data = {}

        token = login_data.get("token", "")
        login_user = login_data.get("user", {})

        # If we have a runtime registered, link it to this user
        if token and self._data.get("runtime_user_id"):
            self._link_runtime_to_user(token, self._data["runtime_user_id"])

        # Update identity
        self._data.update({
            "signed_up": True,
            "username": username,
            "email": email,
            "user_id": login_user.get("id") or cloud_user_id,
            "signed_up_at": time.time(),
        })
        self._save()

        if token:
            self._token = token
            self._save_token(token)
            logger.info("Signed up and logged in as: %s (%s)", username, email)
        else:
            logger.info("Signed up as: %s (%s) — login separately to get token", username, email)

        return dict(self._data)

    def login(self, email: str, password: str) -> dict[str, Any]:
        """Log into an existing account.

        Args:
            email: Account email.
            password: Account password.

        Returns:
            Updated identity data dict.

        Raises:
            ValueError: If login fails (bad credentials, etc.)
        """
        if not email or "@" not in email:
            raise ValueError("Valid email is required")
        if not password:
            raise ValueError("Password is required")

        email = email.strip().lower()

        payload = json.dumps({
            "email": email,
            "password": password,
        }).encode("utf-8")

        try:
            req = urllib.request.Request(
                f"{self._cloud_url}/auth/login",
                data=payload,
                headers={"Content-Type": "application/json"},
            )
            with urllib.request.urlopen(req, timeout=15) as resp:
                data = json.loads(resp.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            body = e.read().decode("utf-8", errors="replace")
            try:
                detail = json.loads(body).get("detail", body)
            except (json.JSONDecodeError, AttributeError):
                detail = body
            raise ValueError(f"Login failed: {detail}")
        except urllib.error.URLError as e:
            raise ValueError(f"Cannot reach cloud server: {e.reason}")

        token = data.get("token", "")
        user = data.get("user", {})

        if not token:
            raise ValueError("Login failed: no token received")

        # Update identity
        self._data.update({
            "signed_up": True,
            "username": user.get("name", email.split("@")[0]),
            "email": user.get("email", email),
            "user_id": user.get("id", ""),
            "signed_up_at": self._data.get("signed_up_at", time.time()),
        })
        self._save()

        self._token = token
        self._save_token(token)

        logger.info("Logged in as: %s (%s)", self.display_name, self.email)
        return dict(self._data)

    def logout(self) -> None:
        """Log out. Clears JWT but keeps the anonymous identity.

        After logout, the user reverts to their anonymous name
        and can still use local features. They can login again
        or signup with a different account.
        """
        self._token = None
        if self._token_path.exists():
            try:
                self._token_path.unlink()
            except OSError:
                pass

        # Revert to anonymous state but keep the anonymous_name
        anon_name = self._data.get("anonymous_name", "")
        identity_id = self._data.get("id", "")
        created_at = self._data.get("created_at", time.time())

        self._data = {
            "id": identity_id,
            "anonymous_name": anon_name,
            "created_at": created_at,
            "signed_up": False,
        }
        self._save()
        logger.info("Logged out. Reverted to anonymous: %s", anon_name)

    def get_auth_headers(self) -> dict[str, str]:
        """Get Authorization headers for cloud API calls.

        Returns:
            Dict with Bearer token header, or empty dict if no token.
        """
        if self._token:
            return {"Authorization": f"Bearer {self._token}"}
        return {}

    # ── Private Helpers ───────────────────────────────────────────

    def _load(self) -> None:
        """Load identity from disk."""
        if self._path.exists():
            try:
                self._data = json.loads(self._path.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                self._data = {}
        self._load_token()

    def _load_token(self) -> None:
        """Load JWT token from disk."""
        if self._token_path.exists():
            try:
                tok = self._token_path.read_text(encoding="utf-8").strip()
                if tok:
                    self._token = tok
            except OSError:
                pass

    def _save(self) -> None:
        """Save identity to disk."""
        self._dir.mkdir(parents=True, exist_ok=True)
        self._path.write_text(
            json.dumps(self._data, indent=2),
            encoding="utf-8",
        )

    def _save_token(self, token: str) -> None:
        """Save JWT token to disk."""
        self._dir.mkdir(parents=True, exist_ok=True)
        self._token_path.write_text(token, encoding="utf-8")

    def _auto_register(self) -> None:
        """Register anonymously with the cloud to get a JWT.

        Uses the /v1/auth/register endpoint with a runtime_id.
        """
        runtime_id = self._data.get("id", secrets.token_hex(16))
        payload = json.dumps({
            "runtime_id": runtime_id,
        }).encode("utf-8")

        try:
            req = urllib.request.Request(
                f"{self._cloud_url}/v1/auth/register",
                data=payload,
                headers={"Content-Type": "application/json"},
            )
            with urllib.request.urlopen(req, timeout=10) as resp:
                data = json.loads(resp.read().decode("utf-8"))

            token = data.get("token", "")
            user_id = data.get("user_id", "")

            if token:
                self._token = token
                self._save_token(token)
                self._data["runtime_user_id"] = user_id
                self._save()
                logger.info("Auto-registered with cloud (user_id=%s)", user_id)
            else:
                logger.warning("Cloud registration returned no token")
        except Exception as e:
            logger.warning("Cloud auto-registration failed (offline?): %s", e)

    def _link_runtime_to_user(self, token: str, runtime_user_id: str) -> None:
        """Attempt to link the anonymous runtime record to the real user."""
        try:
            # This would require a cloud endpoint — skip silently if it fails
            pass
        except Exception:
            pass
