"""File-backed tenant registry with CRUD and mtime caching (Phase 2A).

Storage layout::

    {data_dir}/tenants/
        registry.json          ← list of TenantConfig dicts
        {tenant_id}/           ← per-tenant data (created on register)
"""
from __future__ import annotations

import json
import os
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from .context import DEFAULT_TENANT_ID, TenantContext


@dataclass
class TenantConfig:
    """Persisted tenant configuration (mutable, stored in registry.json)."""

    tenant_id: str
    display_name: str = ""
    tier: str = "CE"
    connection_config: Dict[str, Any] = field(default_factory=dict)
    created_at: str = ""
    updated_at: str = ""


class TenantRegistry:
    """File-backed tenant CRUD with mtime-based caching."""

    def __init__(self, data_dir: Path = Path("data/tenants"), base_dir: Optional[Path] = None):
        # Backward-compat: legacy callers passed base_dir=<root>; canonical is data_dir=<root>/tenants.
        if base_dir is not None:
            self.data_dir = Path(base_dir) / "tenants"
        else:
            self.data_dir = Path(data_dir)
        self.data_dir.mkdir(parents=True, exist_ok=True)
        self.file_path = self.data_dir / "registry.json"
        self._cache: List[TenantConfig] = []
        self._cache_mtime: float = 0.0

    # ------------------------------------------------------------------
    # Public CRUD
    # ------------------------------------------------------------------

    def register(
        self,
        tenant_id: str,
        display_name: str = "",
        tier: str = "CE",
        connection_config: Optional[Dict[str, Any]] = None,
    ) -> TenantConfig:
        """Create a new tenant. Raises ValueError if already exists."""
        configs = self._load()
        if any(c.tenant_id == tenant_id for c in configs):
            raise ValueError(f"Tenant '{tenant_id}' already registered")

        now = datetime.now(timezone.utc).isoformat()
        cfg = TenantConfig(
            tenant_id=tenant_id,
            display_name=display_name or tenant_id,
            tier=tier,
            connection_config=connection_config or {},
            created_at=now,
            updated_at=now,
        )
        configs.append(cfg)
        self._save(configs)

        # Create per-tenant data directory
        (self.data_dir / tenant_id).mkdir(parents=True, exist_ok=True)
        return cfg

    def get(self, tenant_id: str) -> Optional[TenantConfig]:
        """Return config for *tenant_id*, or None."""
        for c in self._load():
            if c.tenant_id == tenant_id:
                return c
        return None

    def list_all(self) -> List[TenantConfig]:
        """Return all registered tenants."""
        return list(self._load())

    def update(self, tenant_id: str, **kwargs: Any) -> TenantConfig:
        """Update fields on an existing tenant. Raises ValueError if missing."""
        configs = self._load()
        for i, c in enumerate(configs):
            if c.tenant_id == tenant_id:
                allowed = {"display_name", "tier", "connection_config"}
                for key, value in kwargs.items():
                    if key not in allowed:
                        raise ValueError(f"Cannot update field '{key}'")
                    setattr(c, key, value)
                c.updated_at = datetime.now(timezone.utc).isoformat()
                configs[i] = c
                self._save(configs)
                return c
        raise ValueError(f"Tenant '{tenant_id}' not found")

    def delete(self, tenant_id: str) -> bool:
        """Remove a tenant from the registry. Returns True if found."""
        configs = self._load()
        before = len(configs)
        configs = [c for c in configs if c.tenant_id != tenant_id]
        if len(configs) == before:
            return False
        self._save(configs)
        return True

    # ------------------------------------------------------------------
    # Legacy API compatibility
    # ------------------------------------------------------------------

    def set_tenant_assets(self, tenant_id: str, assets: List[str]) -> None:
        """Legacy helper to persist simple tenant asset lists."""
        cfg = self.get(tenant_id)
        if cfg is None:
            self.register(tenant_id=tenant_id, display_name=tenant_id)
        tenant_dir = self.data_dir / tenant_id
        tenant_dir.mkdir(parents=True, exist_ok=True)
        (tenant_dir / "assets.json").write_text(
            json.dumps(sorted(set(assets)), indent=2),
            encoding="utf-8",
        )

    def get_tenant_assets(self, tenant_id: str) -> List[str]:
        """Legacy helper to load simple tenant asset lists."""
        path = self.data_dir / tenant_id / "assets.json"
        if not path.exists():
            return []
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
            if isinstance(payload, list):
                return [str(x) for x in payload]
        except Exception:
            return []
        return []

    # ------------------------------------------------------------------
    # Resolve — build a TenantContext from stored config
    # ------------------------------------------------------------------

    def resolve(self, tenant_id: str) -> TenantContext:
        """Build a ``TenantContext`` for *tenant_id*.

        Falls back to a default context if the tenant is not registered.
        """
        cfg = self.get(tenant_id)
        if cfg is None:
            if tenant_id == DEFAULT_TENANT_ID:
                return TenantContext.default()
            # Unknown tenant — return a minimal context so tools don't break
            return TenantContext(
                tenant_id=tenant_id,
                display_name=tenant_id,
                tier="CE",
                data_dir=self.data_dir / tenant_id,
            )

        return TenantContext(
            tenant_id=cfg.tenant_id,
            display_name=cfg.display_name,
            tier=cfg.tier,
            data_dir=self.data_dir / cfg.tenant_id,
            connection_config=cfg.connection_config,
        )

    # ------------------------------------------------------------------
    # Persistence (mtime-cached JSON)
    # ------------------------------------------------------------------

    def _load(self) -> List[TenantConfig]:
        if not self.file_path.exists():
            return []

        mtime = os.path.getmtime(self.file_path)
        if mtime == self._cache_mtime and self._cache:
            return self._cache

        raw = json.loads(self.file_path.read_text(encoding="utf-8"))
        self._cache = [TenantConfig(**item) for item in raw]
        self._cache_mtime = mtime
        return self._cache

    def _save(self, configs: List[TenantConfig]) -> None:
        payload = [asdict(c) for c in configs]
        self.file_path.write_text(
            json.dumps(payload, indent=2), encoding="utf-8"
        )
        self._cache = configs
        self._cache_mtime = os.path.getmtime(self.file_path)
