import base64
from functools import lru_cache
from pathlib import Path, PurePosixPath

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", extra="ignore")

    app_env: str = Field(default="development", pattern=r"^(development|test|production)$")
    computer_service_token: str | None = Field(default=None, min_length=32)
    computer_service_token_file: str | None = None
    computer_capability_public_key: str | None = None
    computer_capability_public_key_file: str | None = None
    computer_capability_key_id: str = Field(default="computer-capability-v1", min_length=1, max_length=80)
    browser_profile_root: str = "/data/browser-profiles"
    computer_artifact_root: str = "/data/computer-artifacts"
    browser_headless: bool = True
    browser_action_timeout_ms: int = Field(default=30_000, ge=1_000, le=120_000)
    browser_max_sessions: int = Field(default=4, ge=1, le=32)

    def read_service_token(self) -> str | None:
        if self.computer_service_token_file:
            try:
                token = Path(self.computer_service_token_file).read_text(
                    encoding="utf-8"
                ).strip()
            except OSError:
                return None
            return token if len(token) >= 32 else None
        return self.computer_service_token

    def read_capability_public_key(self) -> bytes | None:
        encoded = self.computer_capability_public_key
        if self.computer_capability_public_key_file:
            try:
                encoded = Path(self.computer_capability_public_key_file).read_text(
                    encoding="utf-8"
                ).strip()
            except OSError:
                return None
        if not encoded:
            return None
        try:
            key = base64.urlsafe_b64decode(encoded + "=" * (-len(encoded) % 4))
        except (ValueError, TypeError):
            return None
        return key if len(key) == 32 else None

    def production_configuration_errors(self) -> list[str]:
        if self.app_env != "production":
            return []
        errors: list[str] = []
        if not self.computer_service_token_file or not self.read_service_token():
            errors.append("COMPUTER_SERVICE_TOKEN_FILE must reference a valid mounted token")
        if (
            not self.computer_capability_public_key_file
            or not self.read_capability_public_key()
        ):
            errors.append(
                "COMPUTER_CAPABILITY_PUBLIC_KEY_FILE must reference a valid mounted key"
            )
        profile_root = PurePosixPath(self.browser_profile_root)
        artifact_root = PurePosixPath(self.computer_artifact_root)
        for label, root in (
            ("BROWSER_PROFILE_ROOT", profile_root),
            ("COMPUTER_ARTIFACT_ROOT", artifact_root),
        ):
            if not root.is_absolute() or root == PurePosixPath("/") or str(root).startswith("/tmp"):
                errors.append(f"{label} must be a dedicated absolute persistent path")
        if profile_root == artifact_root:
            errors.append("Browser profiles and artifacts must use different storage roots")
        if not self.browser_headless:
            errors.append("BROWSER_HEADLESS must remain enabled in the isolated worker")
        return errors

    def require_production_configuration(self) -> None:
        errors = self.production_configuration_errors()
        if errors:
            raise RuntimeError(
                "Production Computer Agent configuration is unsafe: " + "; ".join(errors)
            )


@lru_cache
def get_settings() -> Settings:
    return Settings()  # type: ignore[call-arg]
