"""Local CLI configuration and credentials storage."""

from __future__ import annotations

import json
import os
import stat
import sys
import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Dict, Optional

try:
    import fcntl
except ImportError:
    fcntl = None  # type: ignore[assignment]  # Windows

# Support both old and new config directories
CONFIG_DIR = Path.home() / ".hikaflow"
LEGACY_CONFIG_DIR = Path.home() / ".autodebug"
CREDENTIALS_PATH = CONFIG_DIR / "credentials.json"
LEGACY_CREDENTIALS_PATH = LEGACY_CONFIG_DIR / "credentials.json"

# Token expiration buffer (refresh 5 minutes before expiry)
TOKEN_REFRESH_BUFFER = 300

# Lock for thread-safe token refresh
_refresh_lock = threading.Lock()


def _migrate_legacy_config() -> None:
    """Migrate credentials from ~/.autodebug to ~/.hikaflow if needed."""
    if LEGACY_CREDENTIALS_PATH.exists() and not CREDENTIALS_PATH.exists():
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        legacy_data = json.loads(LEGACY_CREDENTIALS_PATH.read_text(encoding="utf-8"))
        CREDENTIALS_PATH.write_text(json.dumps(legacy_data, indent=2, sort_keys=True), encoding="utf-8")
        # Set restrictive permissions on credentials file
        os.chmod(CREDENTIALS_PATH, stat.S_IRUSR | stat.S_IWUSR)


def load_credentials() -> Dict[str, Any]:
    """Load credentials from config file."""
    _migrate_legacy_config()
    if not CREDENTIALS_PATH.exists():
        return {}
    try:
        return json.loads(CREDENTIALS_PATH.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, ValueError):
        return {}


def save_credentials(data: Dict[str, Any]) -> None:
    """Save credentials to config file with secure permissions.

    Merges with existing credentials to prevent accidental data loss.
    Callers can pass partial dicts (e.g. just {"project_id": "..."})
    without nuking tokens or other stored fields.

    Uses file lock for cross-process safety (fcntl on POSIX, thread lock on Windows)
    and atomic write-to-temp-then-rename.
    """
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    def _write_locked() -> None:
        existing = load_credentials()
        existing.update(data)
        fd, tmp_path = tempfile.mkstemp(dir=str(CREDENTIALS_PATH.parent))
        try:
            with os.fdopen(fd, 'w') as f:
                json.dump(existing, f, indent=2, sort_keys=True)
            os.chmod(tmp_path, stat.S_IRUSR | stat.S_IWUSR)
            os.replace(tmp_path, str(CREDENTIALS_PATH))
        except Exception:
            os.unlink(tmp_path)
            raise

    if fcntl is not None:
        lock_path = CONFIG_DIR / ".credentials.lock"
        lock_path.touch(mode=0o600, exist_ok=True)
        with open(lock_path, "w") as lockfile:
            fcntl.flock(lockfile, fcntl.LOCK_EX)
            try:
                _write_locked()
            finally:
                fcntl.flock(lockfile, fcntl.LOCK_UN)
    else:
        # Windows: fall back to thread lock only (no cross-process lock)
        with _refresh_lock:
            _write_locked()


def clear_credentials() -> None:
    """Clear all stored credentials."""
    if CREDENTIALS_PATH.exists():
        CREDENTIALS_PATH.unlink()


_PRODUCTION_API_BASE = "https://get.hikaflow.com"


def get_api_base() -> str:
    """Get the API base URL from config or environment.

    Priority: HIKAFLOW_ENDPOINT env > AUTODEBUG_ENDPOINT env > credentials > production default.
    If the stored api_base is localhost and no env override is set, fall back to production.
    """
    env_base = os.environ.get("HIKAFLOW_ENDPOINT") or os.environ.get("AUTODEBUG_ENDPOINT")
    if env_base:
        return env_base
    creds = load_credentials()
    stored = creds.get("api_base", "")
    if stored and "localhost" not in stored and "127.0.0.1" not in stored:
        return stored
    # Stored base is localhost or empty — use production default
    return _PRODUCTION_API_BASE


def get_valid_token() -> Optional[str]:
    """Get a valid access token, refreshing if needed.

    Priority:
    1. HIKAFLOW_TOKEN environment variable
    2. AUTODEBUG_TOKEN environment variable (legacy)
    3. Stored token with auto-refresh

    Returns:
        Valid JWT access token or None if not authenticated
    """
    # Check environment variables first
    env_token = os.environ.get("HIKAFLOW_TOKEN") or os.environ.get("AUTODEBUG_TOKEN")
    if env_token:
        return env_token

    creds = load_credentials()

    # Check for stored access token
    access_token = creds.get("access_token") or creds.get("token")
    if not access_token:
        return None

    # Check if we have expiration info
    expires_at = creds.get("expires_at")
    if expires_at:
        current_time = time.time()
        # If token expires in less than 5 minutes, try to refresh
        if current_time >= expires_at - TOKEN_REFRESH_BUFFER:
            with _refresh_lock:
                # Re-read credentials inside lock in case another thread refreshed
                creds = load_credentials()
                expires_at = creds.get("expires_at")
                access_token = creds.get("access_token") or creds.get("token")
                current_time = time.time()
                if expires_at and current_time >= expires_at - TOKEN_REFRESH_BUFFER:
                    refreshed_token = _refresh_token(creds)
                    if refreshed_token:
                        return refreshed_token
                    # If refresh failed but token is still valid, use it
                    if current_time < expires_at:
                        return access_token
                    return None
                # Another thread already refreshed successfully
                return access_token

    return access_token


def _refresh_token(creds: Dict[str, Any]) -> Optional[str]:
    """Attempt to refresh the access token.

    Args:
        creds: Current credentials dict

    Returns:
        New access token if refresh succeeded, None otherwise
    """
    import requests

    refresh_token = creds.get("refresh_token")
    if not refresh_token:
        return None

    api_base = get_api_base()

    try:
        response = requests.post(
            f"{api_base.rstrip('/')}/v1/cli/token/refresh",
            json={"refresh_token": refresh_token},
            timeout=10,
        )

        if response.status_code == 200:
            data = response.json()
            # Update stored credentials
            creds["access_token"] = data["access_token"]
            creds["expires_at"] = data["expires_at"]
            if "refresh_token" in data:
                # Token rotation - new refresh token issued
                creds["refresh_token"] = data["refresh_token"]
            save_credentials(creds)
            return data["access_token"]

    except Exception:
        pass  # Refresh failed, caller will handle

    return None


def store_tokens(access_token: str, refresh_token: str, expires_at: float) -> None:
    """Store OAuth tokens from a successful login.

    Args:
        access_token: The JWT access token
        refresh_token: The refresh token for obtaining new access tokens
        expires_at: Unix timestamp when access token expires
    """
    creds = load_credentials()
    creds["access_token"] = access_token
    creds["refresh_token"] = refresh_token
    creds["expires_at"] = expires_at
    # Remove legacy token field if present
    creds.pop("token", None)
    save_credentials(creds)


def is_authenticated() -> bool:
    """Check if user is authenticated with a valid token."""
    return get_valid_token() is not None
