import json
from functools import lru_cache
from pathlib import Path

from fastapi import HTTPException
from pydantic import ValidationError

from .exceptions import (
    TenantConfigNotFoundError,
    TenantConfigValidationError,
)
from .logging import logger
from .models import TenantConfig


class TenantConfigService:
    def __init__(self, secret_dir: str | Path) -> None:
        self.secret_dir = Path(secret_dir)

    def _secret_path(self, tenant: str) -> Path:
        return self.secret_dir / f"tenant_{tenant}"

    def _read_json_file(self, path: Path) -> dict:
        return json.loads(path.read_text(encoding="utf-8"))

    def _load_secret(self, tenant: str) -> TenantConfig:
        path = self._secret_path(tenant)

        logger.info(
            "tenant_secret_config_load_attempt",
            extra={"tenant": tenant, "path": str(path)},
        )

        if not path.exists():
            logger.error(
                "tenant_secret_config_missing",
                extra={"tenant": tenant, "path": str(path)},
            )
            raise TenantConfigNotFoundError(
                f"Secret non trovato per tenant '{tenant}'"
            )

        try:
            raw = self._read_json_file(path)
            cfg = TenantConfig.model_validate(raw)
        except ValidationError as exc:
            logger.exception(
                "tenant_secret_config_invalid",
                extra={"tenant": tenant, "path": str(path)},
            )
            raise TenantConfigValidationError(
                f"Secret non valido per tenant '{tenant}': {exc}"
            ) from exc
        except json.JSONDecodeError as exc:
            logger.exception(
                "tenant_secret_config_json_invalid",
                extra={"tenant": tenant, "path": str(path)},
            )
            raise TenantConfigValidationError(
                f"JSON secret non valido per tenant '{tenant}': {exc}"
            ) from exc

        logger.info(
            "tenant_secret_config_loaded",
            extra={"tenant": tenant},
        )
        return cfg
    
    
    @lru_cache(maxsize=128)
    def get(self, tenant: str) -> TenantConfig:
        try:
            return self._load_secret(tenant)
        except Exception as e:
            raise TenantConfigValidationError(str(e))

    def list_tenants(self) -> list[str]:
        return sorted(
            p.name.removeprefix("tenant_")
            for p in self.secret_dir.iterdir()
            if p.is_file() and p.name.startswith("tenant_")
        )

    def invalidate(self):
        self.get.cache_clear()
        
    @staticmethod
    def require_section(value, tenant: str, section: str):
        if value is None:
            raise HTTPException(
                status_code=500,
                detail=f"Configurazione '{section}' mancante per tenant '{tenant}'",
            )
        return value
    
    def get_db_url(self, tenant: str) -> str:
        return self.get(tenant).db.url.get_secret_value()

    def get_keycloak_client_secret(self, tenant: str) -> str:
        return self.get(tenant).keycloak_admin.client_secret.get_secret_value()

    def get_pagopa_username(self, tenant: str) -> str:
        return self.get(tenant).pagopa.username

    def get_pagopa_password(self, tenant: str) -> str:
        return self.get(tenant).pagopa.password.get_secret_value()

    def healthcheck(self, tenant: str) -> bool:
        try:
            self.get(tenant)
            return True
        except Exception:
            logger.exception(
                "tenant_config_healthcheck_failed",
                extra={"tenant": tenant},
            )
            return False