"""Maeris AI configuration — repo scoped by default.

Tracks whether the user is running Maeris with their own LLM (external_llm)
or with a Maeris-hosted AI subscription (maeris).

Stored per repository so different projects can use different AI modes.
Default location:
``~/.maeris/repos/<sha256(cwd)[:16]>/ai_config.json``

Resolution order for config directory:
1. Explicit ``config_dir`` argument
2. ``MAERIS_CONFIG_DIR`` (preferred, set by .mcp.json / CLI)
3. Repo-hash path under ``~/.maeris/repos/``

Legacy fallback:
- If repo-scoped file is missing, reads ``~/.maeris/ai_config.json`` for
  backward compatibility. New writes always go to repo-scoped config.

File format::

    {
        "ai_mode": "maeris" | "external_llm",
        "auth_token": "maeris_xxxxx",          # only present in maeris mode
        "plan": "developer",                   # only present in maeris mode
        "token_expires_at": "2026-04-01T..."   # optional, set by server
    }
"""

from __future__ import annotations

import json
import os
import hashlib
from datetime import datetime
from pathlib import Path
from typing import Any

import structlog

logger = structlog.get_logger(__name__)

AI_MODE_EXTERNAL = "external_llm"
AI_MODE_MAERIS = "maeris"


class AIConfig:
    """Read and write repo-scoped Maeris AI mode and subscription token."""

    def __init__(self, config_dir: Path | None = None) -> None:
        if config_dir:
            base = config_dir
        elif env_dir := os.environ.get("MAERIS_CONFIG_DIR"):
            base = Path(env_dir)
        else:
            home_override = os.environ.get("MAERIS_HOME", "")
            maeris_home = Path(home_override) if home_override else Path.home() / ".maeris"
            repo_hash = hashlib.sha256(str(Path.cwd().resolve()).encode()).hexdigest()[:16]
            base = maeris_home / "repos" / repo_hash
        self.config_path = base / "ai_config.json"
        self.legacy_config_path = (Path.home() / ".maeris") / "ai_config.json"

    # ------------------------------------------------------------------
    # Read helpers
    # ------------------------------------------------------------------

    def load(self) -> dict[str, Any]:
        """Load the raw config dict (empty dict if file missing or corrupt).

        Falls back to legacy global path only when repo-scoped file is missing.
        """
        path = self.config_path
        if not path.exists() and self.legacy_config_path.exists():
            path = self.legacy_config_path
        if not path.exists():
            return {}
        try:
            return json.loads(path.read_text())
        except (json.JSONDecodeError, OSError) as e:
            logger.error("ai_config_load_error", path=str(path), error=str(e))
            return {}

    def get_ai_mode(self) -> str | None:
        """Return current ai_mode, or None if not configured."""
        return self.load().get("ai_mode")

    def get_auth_token(self) -> str | None:
        """Return the Maeris AI bearer token, or None."""
        return self.load().get("auth_token")

    def get_plan(self) -> str | None:
        """Return the subscription plan name (e.g. 'developer'), or None."""
        return self.load().get("plan")

    def is_maeris_ai(self) -> bool:
        """True when the user has chosen Maeris-hosted AI."""
        return self.get_ai_mode() == AI_MODE_MAERIS

    def is_external_llm(self) -> bool:
        """True when the user has chosen to use their own LLM (Claude / Codex / Cursor)."""
        return self.get_ai_mode() == AI_MODE_EXTERNAL

    def is_configured(self) -> bool:
        """True if ai_mode has been set (regardless of which mode)."""
        return self.get_ai_mode() is not None

    def is_authenticated(self) -> bool:
        """True if Maeris AI token is present and not expired.

        Returns False for external_llm mode (no token required there).
        """
        data = self.load()
        if data.get("ai_mode") != AI_MODE_MAERIS:
            return False
        if not data.get("auth_token"):
            return False

        exp = data.get("token_expires_at")
        if exp:
            try:
                if datetime.fromisoformat(exp) <= datetime.now():
                    return False
            except (ValueError, TypeError):
                pass  # treat unparseable expiry as still valid

        return True

    # ------------------------------------------------------------------
    # Write helpers
    # ------------------------------------------------------------------

    def save_external_llm(self) -> None:
        """Set mode to external_llm. Removes any existing Maeris AI token."""
        data = self.load()
        data["ai_mode"] = AI_MODE_EXTERNAL
        data.pop("auth_token", None)
        data.pop("plan", None)
        data.pop("token_expires_at", None)
        self._write(data)
        logger.info("ai_mode_set", mode=AI_MODE_EXTERNAL)

    def save_maeris_mode(self) -> None:
        """Set mode to maeris. No separate token needed — portal token is used."""
        data = self.load()
        data["ai_mode"] = AI_MODE_MAERIS
        data.pop("auth_token", None)
        data.pop("plan", None)
        data.pop("token_expires_at", None)
        self._write(data)
        logger.info("ai_mode_set", mode=AI_MODE_MAERIS)

    def save_maeris_token(
        self,
        auth_token: str,
        plan: str = "developer",
        expires_at: str | None = None,
    ) -> None:
        """Store Maeris AI token and set mode to maeris.

        Args:
            auth_token: Bearer token returned by the device flow.
            plan: Subscription plan returned by the server (default: developer).
            expires_at: Optional ISO datetime string when the token expires.
        """
        data = self.load()
        data["ai_mode"] = AI_MODE_MAERIS
        data["auth_token"] = auth_token
        data["plan"] = plan
        if expires_at:
            data["token_expires_at"] = expires_at
        else:
            data.pop("token_expires_at", None)
        self._write(data)
        logger.info("maeris_ai_token_saved", plan=plan)

    def clear_ai_token(self) -> None:
        """Remove the Maeris AI token but keep the ai_mode setting."""
        data = self.load()
        data.pop("auth_token", None)
        data.pop("plan", None)
        data.pop("token_expires_at", None)
        self._write(data)
        logger.info("maeris_ai_token_cleared")

    def clear(self) -> None:
        """Delete the AI config file entirely."""
        if self.config_path.exists():
            self.config_path.unlink()
            logger.info("ai_config_cleared")

    # ------------------------------------------------------------------
    # Internal
    # ------------------------------------------------------------------

    def _write(self, data: dict[str, Any]) -> None:
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
        self.config_path.write_text(json.dumps(data, indent=2))
        try:
            self.config_path.chmod(0o600)
        except (OSError, NotImplementedError):
            # Windows doesn't support chmod the same way
            pass
