# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Auto-setup engine — bulk credential application and service testing.

Takes a bag of credentials and writes them all to ``.env`` in one shot,
then optionally tests each service for connectivity.
"""

from __future__ import annotations

import logging
import os
from pathlib import Path

import yaml

from ._helpers import _test_http_service, _update_env_file
from .service_catalog import (
    SERVICE_CATALOG,
    ServiceEntry,
    get_services_for_job_class,
)

logger = logging.getLogger(__name__)


class AutoSetupEngine:
    """Bulk credential writer + service tester."""

    def __init__(self, data_dir: Path | None = None):
        self.data_dir = data_dir or Path.home() / ".familiar"
        self.env_path = self.data_dir / ".env"
        self.config_path = self.data_dir / "config.yaml"

    # ── Recommendations ──────────────────────────────────────────────

    def get_recommended_services(self, job_class: str) -> list[ServiceEntry]:
        """Return services relevant to *job_class*, simplest first."""
        return get_services_for_job_class(job_class)

    # ── Status ───────────────────────────────────────────────────────

    def get_setup_status(self) -> dict[str, dict]:
        """Check which services are already configured by scanning .env.

        Returns ``{service_key: {"configured": bool, "env_vars": {var: bool}}}``.
        """
        env_values = self._read_env_file()
        result: dict[str, dict] = {}
        for key, entry in SERVICE_CATALOG.items():
            if not entry.env_vars:
                result[key] = {"configured": False, "env_vars": {}}
                continue
            var_status = {v: bool(env_values.get(v)) for v in entry.env_vars}
            configured = all(var_status.values())
            result[key] = {"configured": configured, "env_vars": var_status}
        return result

    # ── Bulk apply ───────────────────────────────────────────────────

    def apply_credentials(self, credentials: dict[str, dict[str, str]]) -> dict[str, dict]:
        """Bulk-apply credentials.

        *credentials*: ``{service_key: {ENV_VAR: value, ...}}``.
        Writes to ``.env`` via ``_update_env_file()``.
        Returns per-service results: ``{key: {"saved": bool, "error": str|None}}``.
        """
        results: dict[str, dict] = {}
        for service_key, env_map in credentials.items():
            entry = SERVICE_CATALOG.get(service_key)
            if entry is None:
                results[service_key] = {
                    "saved": False,
                    "error": f"Unknown service: {service_key}",
                }
                continue

            # Filter out empty values
            clean = {k: v for k, v in env_map.items() if v and v.strip()}
            if not clean:
                results[service_key] = {"saved": False, "error": "No credentials provided"}
                continue

            try:
                _update_env_file(self.env_path, clean)
                # Also set in current process so tests can use them
                for k, v in clean.items():
                    os.environ[k] = v
                results[service_key] = {"saved": True, "error": None}
            except Exception as exc:
                results[service_key] = {"saved": False, "error": str(exc)}

        return results

    # ── Test ─────────────────────────────────────────────────────────

    def test_service(self, service_key: str) -> tuple[bool, str]:
        """Test a single service's connectivity.

        Returns ``(success, message)``.
        """
        entry = SERVICE_CATALOG.get(service_key)
        if entry is None:
            return False, f"Unknown service: {service_key}"

        if not entry.test_url:
            return True, "No connectivity test available (assumed OK)"

        if not entry.env_vars:
            return True, "No credentials required"

        # Resolve the primary token for URL / header substitution
        token = os.environ.get(entry.env_vars[0], "")
        if not token:
            return False, f"{entry.env_vars[0]} not set"

        headers = _resolve_placeholders(entry.test_headers, token)
        params = _resolve_placeholders(entry.test_params, token)

        return _test_http_service(
            entry.test_url,
            headers=headers,
            params=params,
        )

    def test_all_configured(self) -> dict[str, tuple[bool, str]]:
        """Test every service that has credentials set.

        Returns ``{service_key: (ok, message)}``.
        """
        status = self.get_setup_status()
        results: dict[str, tuple[bool, str]] = {}
        for key, info in status.items():
            if info["configured"]:
                results[key] = self.test_service(key)
        return results

    # ── Profile helpers ──────────────────────────────────────────────

    def save_profile(self, profile: dict) -> None:
        """Save organization/user profile to config.yaml."""
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
        data: dict = {}
        if self.config_path.exists():
            data = yaml.safe_load(self.config_path.read_text()) or {}
        if "organization" not in data:
            data["organization"] = {}
        data["organization"].update({k: v for k, v in profile.items() if v and str(v).strip()})
        self.config_path.write_text(yaml.dump(data, default_flow_style=False))

    # ── Internal ─────────────────────────────────────────────────────

    def _read_env_file(self) -> dict[str, str]:
        """Parse the .env file into a dict."""
        values: dict[str, str] = {}
        if not self.env_path.exists():
            return values
        for line in self.env_path.read_text().splitlines():
            stripped = line.strip()
            if not stripped or stripped.startswith("#") or "=" not in stripped:
                continue
            key, _, val = stripped.partition("=")
            values[key.strip()] = val.strip()
        return values


def _resolve_placeholders(mapping: dict[str, str] | None, token: str) -> dict[str, str] | None:
    """Replace ``{token}`` in header/param values with the actual token."""
    if not mapping:
        return mapping
    return {k: v.replace("{token}", token) for k, v in mapping.items()}
