from __future__ import annotations

import hashlib
import json
import os
import tempfile
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Callable

from claude_identity import ClaudeTokenIdentity

LIVE_CLAUDE_HOME: Path = Path.home() / ".claude"
IDENTITY_PIN_FILENAME = "claude-account-identity.json"


class CredentialAuthority(Enum):
    """Every credentials file belongs to the vendor CLI: Anthropic refresh tokens are
    one-time-use, and Claude Code sessions rotate account-home files directly via
    CLAUDE_CONFIG_DIR, so a second rotator replays a consumed token and the provider
    revokes the whole grant."""

    VENDOR_MANAGED = "vendor-managed"


@dataclass(frozen=True)
class ResolvedCredentials:
    path: Path
    authority: CredentialAuthority

    @property
    def read_only(self) -> bool:
        return True


def resolve_credentials(
    account_home: Path,
    live_home: Path | None = None,
    identify: Callable[[Path], str | None] | None = None,
) -> ResolvedCredentials:
    own = account_home / ".credentials.json"
    home = LIVE_CLAUDE_HOME if live_home is None else live_home
    live = home / ".credentials.json"
    if live != own and live_owner_is(account_home, home, identify):
        return ResolvedCredentials(live, CredentialAuthority.VENDOR_MANAGED)
    return ResolvedCredentials(own, CredentialAuthority.VENDOR_MANAGED)


def live_owner_is(
    account_home: Path,
    live_home: Path | None = None,
    identify: Callable[[Path], str | None] | None = None,
) -> bool:
    """Whether the vendor's live credentials file currently holds this account's grant."""
    home = LIVE_CLAUDE_HOME if live_home is None else live_home
    live = home / ".credentials.json"
    if not _has_tokens(live):
        return False
    account_uuid = pinned_account_uuid(account_home)
    if account_uuid is None:
        return False
    resolve = ClaudeTokenIdentity().account_uuid_for if identify is None else identify
    return account_uuid == resolve(live)


def credentials_fingerprint(path: Path) -> str | None:
    payload = _read_json_object(path)
    if payload is None:
        return None
    oauth = payload.get("claudeAiOauth")
    if not isinstance(oauth, dict):
        return None
    digest = hashlib.sha256()
    for key in ("accessToken", "refreshToken"):
        value = oauth.get(key)
        digest.update(b"\x00" if not isinstance(value, str) else value.encode("utf-8"))
        digest.update(b"\x1f")
    return digest.hexdigest()


def account_uuid_of(home: Path) -> str | None:
    payload = _read_json_object(home / ".claude.json")
    if payload is None:
        return None
    oauth = payload.get("oauthAccount")
    if not isinstance(oauth, dict):
        return None
    account_uuid = oauth.get("accountUuid")
    if isinstance(account_uuid, str) and account_uuid:
        return account_uuid
    return None


def identity_pin_path(account_home: Path) -> Path:
    return account_home.parent / IDENTITY_PIN_FILENAME


def pinned_account_uuid(account_home: Path) -> str | None:
    payload = _read_json_object(identity_pin_path(account_home))
    if payload is None:
        return None
    account_uuid = payload.get("account_uuid")
    return account_uuid if isinstance(account_uuid, str) and account_uuid else None


def pin_account_uuid(account_home: Path, account_uuid: str) -> None:
    if pinned_account_uuid(account_home) is not None:
        return
    path = identity_pin_path(account_home)
    tmp_name: str | None = None
    try:
        fd, tmp_name = tempfile.mkstemp(dir=path.parent, prefix=f".{IDENTITY_PIN_FILENAME}.")
        with os.fdopen(fd, "w", encoding="utf-8") as handle:
            json.dump({"account_uuid": account_uuid}, handle)
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(tmp_name, path)
    except OSError:
        if tmp_name is not None:
            try:
                os.unlink(tmp_name)
            except OSError:
                pass


def _has_tokens(path: Path) -> bool:
    payload = _read_json_object(path)
    if payload is None:
        return False
    oauth = payload.get("claudeAiOauth")
    if not isinstance(oauth, dict):
        return False
    return bool(oauth.get("accessToken")) and bool(oauth.get("refreshToken"))


def _read_json_object(path: Path) -> dict[str, object] | None:
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return None
    return payload if isinstance(payload, dict) else None


__all__ = [
    "LIVE_CLAUDE_HOME",
    "IDENTITY_PIN_FILENAME",
    "CredentialAuthority",
    "ResolvedCredentials",
    "account_uuid_of",
    "credentials_fingerprint",
    "identity_pin_path",
    "live_owner_is",
    "pin_account_uuid",
    "pinned_account_uuid",
    "resolve_credentials",
]
