"""
Local credential storage for Amina CLI.

This module handles storing and retrieving API keys locally.
Validation happens server-side when the key is used.

Credentials are stored in ~/.amina/credentials.json

This is the same pattern used by:
- OpenAI CLI: stores key in ~/.openai
- AWS CLI: stores in ~/.aws/credentials
- Stripe CLI: stores in ~/.config/stripe
"""

import json
from pathlib import Path
from typing import Optional

# Configuration directory and files
CONFIG_DIR = Path.home() / ".amina"
CREDENTIALS_FILE = CONFIG_DIR / "credentials.json"

# API key format validation
API_KEY_PREFIX = "ami_"
API_KEY_MIN_LENGTH = 36  # ami_ + 32 hex chars


class AuthError(Exception):
    """Raised when authentication fails or credentials are missing."""

    pass


def validate_key_format(key: str) -> bool:
    """
    Validate that a key has the correct format.

    Only checks format, NOT validity. Server validates when key is used.

    Args:
        key: The API key to validate

    Returns:
        True if format is valid
    """
    if not key:
        return False
    if not key.startswith(API_KEY_PREFIX):
        return False
    if len(key) < API_KEY_MIN_LENGTH:
        return False
    return True


def set_api_key(key: str) -> None:
    """
    Store an API key locally.

    Format is validated but the key is NOT verified against the server.
    Verification happens when the key is used for the first time.

    Args:
        key: The API key to store

    Raises:
        ValueError: If key format is invalid
    """
    if not validate_key_format(key):
        raise ValueError(
            f"Invalid API key format. Must start with '{API_KEY_PREFIX}' "
            f"and be at least {API_KEY_MIN_LENGTH} characters."
        )

    save_credentials({"api_key": key})


def save_credentials(data: dict) -> None:
    """
    Save credentials to the config file.

    Follows the same security pattern as OpenAI CLI:
    - Directory: 0o700 (owner only)
    - File: 0o600 (owner read/write only)

    Args:
        data: Dictionary of credentials to save
    """
    import os

    # Create config directory with restrictive permissions (owner only)
    CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)

    # Write credentials file atomically with secure permissions
    # This prevents a race condition where the file briefly exists with wrong permissions
    content = json.dumps(data, indent=2)
    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
    with os.fdopen(os.open(CREDENTIALS_FILE, flags, 0o600), "w") as f:
        f.write(content)


def get_credentials() -> dict:
    """
    Load stored credentials.

    Returns:
        Dictionary with 'api_key' and any other stored data

    Raises:
        AuthError: If not authenticated
    """
    if not CREDENTIALS_FILE.exists():
        raise AuthError(
            "Not authenticated. Run: amina auth set-key <your_api_key>\n"
            "Get an API key at: https://app.aminoanalytica.com/settings/api"
        )

    try:
        data = json.loads(CREDENTIALS_FILE.read_text())
        if not data.get("api_key"):
            raise AuthError("Credentials file exists but no API key found.")
        return data
    except json.JSONDecodeError:
        raise AuthError("Credentials file is corrupted. Run: amina auth set-key <key>")


def get_api_key() -> str:
    """
    Get the stored API key.

    Convenience wrapper around get_credentials().

    Returns:
        The stored API key

    Raises:
        AuthError: If not authenticated
    """
    return get_credentials()["api_key"]


def clear_credentials() -> None:
    """
    Remove all stored credentials (logout).

    Idempotent - safe to call even if not authenticated.
    """
    if CREDENTIALS_FILE.exists():
        CREDENTIALS_FILE.unlink()


def get_key_display(key: str) -> str:
    """
    Get a safe display version of an API key.

    Shows prefix and first few chars, hides the rest.

    Args:
        key: The full API key

    Returns:
        Safe display string like "ami_7f3k..."
    """
    if len(key) > 12:
        return f"{key[:12]}..."
    return key


def is_authenticated() -> bool:
    """
    Check if credentials are stored.

    Returns:
        True if credentials file exists with an API key
    """
    try:
        get_credentials()
        return True
    except AuthError:
        return False


def get_session_id() -> Optional[str]:
    """
    Get the linked session/conversation ID if set.

    When a session ID is set, CLI outputs will be linked to that
    web conversation for visibility in the dashboard.

    Returns:
        Session ID or None if not linked
    """
    try:
        data = get_credentials()
        return data.get("session_id")
    except AuthError:
        return None


def set_session_id(session_id: Optional[str]) -> None:
    """
    Link CLI outputs to a web conversation.

    Args:
        session_id: The conversation ID to link, or None to unlink
    """
    try:
        data = get_credentials()
    except AuthError:
        raise AuthError("Must be authenticated before linking a session.")

    if session_id:
        data["session_id"] = session_id
    else:
        data.pop("session_id", None)

    save_credentials(data)


# ═══════════════════════════════════════════════════════════════════════════════
# JOB HISTORY STORAGE
# ═══════════════════════════════════════════════════════════════════════════════

JOBS_FILE = CONFIG_DIR / "jobs.json"
MAX_JOB_HISTORY = 100  # Keep last N jobs


def save_job(job_info: dict) -> None:
    """
    Save a job to the local history.

    Used by background job submission to track jobs for later status checks.

    Args:
        job_info: Dict with job_id, call_id, user_id, tool_name, etc.
    """
    from datetime import datetime
    import os

    # Load existing jobs
    jobs = []
    if JOBS_FILE.exists():
        try:
            jobs = json.loads(JOBS_FILE.read_text())
        except (json.JSONDecodeError, IOError):
            jobs = []

    # Add timestamp and status
    job_info["submitted_at"] = datetime.now().isoformat()
    job_info["status"] = "submitted"

    # Add to front of list (most recent first)
    jobs.insert(0, job_info)

    # Trim to max history
    jobs = jobs[:MAX_JOB_HISTORY]

    # Ensure config directory exists
    CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)

    # Write atomically with secure permissions
    content = json.dumps(jobs, indent=2)
    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
    with os.fdopen(os.open(JOBS_FILE, flags, 0o600), "w") as f:
        f.write(content)


def get_job_history(limit: int = 20) -> list[dict]:
    """
    Get recent jobs from local history.

    Args:
        limit: Maximum number of jobs to return

    Returns:
        List of job info dicts, most recent first
    """
    if not JOBS_FILE.exists():
        return []

    try:
        jobs = json.loads(JOBS_FILE.read_text())
        return jobs[:limit]
    except (json.JSONDecodeError, IOError):
        return []


def get_job_info(job_id: str) -> Optional[dict]:
    """
    Look up a job by ID (full or partial match).

    Args:
        job_id: Full job ID or prefix to match

    Returns:
        Job info dict or None if not found
    """
    jobs = get_job_history(limit=MAX_JOB_HISTORY)

    # Try exact match first
    for job in jobs:
        if job.get("job_id") == job_id:
            return job

    # Try prefix match
    for job in jobs:
        if job.get("job_id", "").startswith(job_id):
            return job

    return None


def update_job_status(job_id: str, status: str) -> None:
    """
    Update the status of a job in local history.

    Args:
        job_id: Job ID to update
        status: New status (e.g., "completed", "failed")
    """
    import os

    if not JOBS_FILE.exists():
        return

    try:
        jobs = json.loads(JOBS_FILE.read_text())
    except (json.JSONDecodeError, IOError):
        return

    for job in jobs:
        if job.get("job_id") == job_id:
            job["status"] = status
            break

    content = json.dumps(jobs, indent=2)
    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
    with os.fdopen(os.open(JOBS_FILE, flags, 0o600), "w") as f:
        f.write(content)
