"""
PyGitWeb configuration: env-driven settings via pydantic-settings, constants, and loadavg.
"""

from __future__ import annotations

import os
import secrets
import sys
from pathlib import Path

from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

# Actions (most identical to gitweb.perl actions)
ACTIONS: set[str] = {
	"blame",
	"blame_incremental",
	"blame_data",
	"blobdiff",
	"blobpatch",
	"blob",
	"blob_plain",
	"commitdiff",
	"commit",
	"heads",
	"history",
	"log",
	"patch",
	"patches",
	"remotes",
	"rss",
	"atom",
	"search",
	"search_help",
	"shortlog",
	"summary",
	"tag",
	"tags",
	"tree",
	"snapshot",
	"object",
	"opml",
	"project_list",
	"project_index",
}

# Map file extension to highlight.js language
BLOB_LANG: dict[str, str] = {
	"py": "python",
	"js": "javascript",
	"ts": "typescript",
	"jsx": "javascript",
	"tsx": "typescript",
	"html": "html",
	"htm": "html",
	"css": "css",
	"scss": "scss",
	"json": "json",
	"md": "markdown",
	"sh": "bash",
	"bash": "bash",
	"yml": "yaml",
	"yaml": "yaml",
	"xml": "xml",
	"go": "go",
	"rs": "rust",
	"java": "java",
	"c": "c",
	"h": "c",
	"cpp": "cpp",
	"cc": "cpp",
	"cxx": "cpp",
	"sql": "sql",
	"r": "r",
	"rb": "ruby",
	"php": "php",
	"swift": "swift",
	"kt": "kotlin",
	"vue": "xml",
	"toml": "toml",
	"ini": "ini",
	"cfg": "ini",
	"dockerfile": "dockerfile",
}


class Settings(BaseSettings):
	"""
	Runtime settings sourced from PYGITWEB_* env vars (and optionally a .env file).

	When AUTH is true, PyGitWeb uses OAuth2 password flow (POST /token), Bearer tokens,
	and browser login. Use ADMIN_USER / ADMIN_PASSWORD for credentials.

	If PYGITWEB_AUTH is omitted entirely (or empty), authentication defaults to enabled
	and a random password is generated once at startup (see stderr).
	Set PYGITWEB_AUTH=0 or PYGITWEB_AUTH=1 explicitly together with ADMIN_* as needed.
	"""

	model_config = SettingsConfigDict(
		env_prefix="PYGITWEB_",
		env_file=".env",
		env_file_encoding="utf-8",
		case_sensitive=False,
		validate_assignment=True,
		extra="ignore",
	)

	PROJECTROOT: str = Field(default_factory=lambda: str(Path.home()))
	PROJECTS_LIST: str = ""  # falls back to PROJECTROOT when blank
	PROJECT_MAXDEPTH: int = 1
	SITE_NAME: str = "PyGitWeb"
	EXPORT_OK: str = ""
	LIST_ALL: bool = True
	STRICT_EXPORT: bool = False
	GIT: str = "git"
	MAXLOAD: float | None = None

	AUTH: bool | None = None
	ADMIN_USER: str = "admin"
	ADMIN_PASSWORD: str | None = None

	@field_validator("AUTH", mode="before")
	@classmethod
	def _coerce_auth(cls, v: object) -> bool | None:
		if v is None:
			return None
		if isinstance(v, str) and v.strip() == "":
			return None
		if isinstance(v, bool):
			return v
		if isinstance(v, int):
			return v != 0
		if isinstance(v, str):
			s = v.strip().lower()
			if s in ("none", "false", "0", "no", "off"):
				return False
			if s in ("true", "1", "yes", "on"):
				return True
			return None
		return bool(v)

	@model_validator(mode="after")
	def _defaults(self) -> Settings:
		if not self.PROJECTS_LIST:
			self.PROJECTS_LIST = self.PROJECTROOT
		return self


def _finalize_auth_settings(s: Settings) -> None:
	implicit_auth = s.AUTH is None
	if implicit_auth:
		s.AUTH = True
	if not s.AUTH:
		return
	generated_password = False
	if not s.ADMIN_PASSWORD:
		s.ADMIN_PASSWORD = secrets.token_urlsafe(16)
		generated_password = True
	if implicit_auth and generated_password:
		print(
			"WARNING: PyGitWeb: PYGITWEB_AUTH was not set; authentication defaults to ON with a random password.\n"
			"         Set PYGITWEB_AUTH=0 to disable, or PYGITWEB_AUTH=1 and set PYGITWEB_ADMIN_USER / "
			"PYGITWEB_ADMIN_PASSWORD for a fixed login.\n",
			file=sys.stderr,
		)
		print(f"  Username: {s.ADMIN_USER}\n  Password: {s.ADMIN_PASSWORD}\n", file=sys.stderr)
	elif generated_password:
		print(
			"PyGitWeb: PYGITWEB_ADMIN_PASSWORD was not set; generated random password. "
			"Set PYGITWEB_ADMIN_PASSWORD for a stable deployment.\n",
			file=sys.stderr,
		)
		print(f"  Password: {s.ADMIN_PASSWORD}\n", file=sys.stderr)


settings = Settings()
_finalize_auth_settings(settings)


def get_loadavg() -> float:
	"""First element of load average, or 0 if unavailable. Port of get_loadavg."""
	try:
		return os.getloadavg()[0]
	except (OSError, AttributeError):
		pass
	try:
		with open("/proc/loadavg") as f:
			return float(f.read().split()[0])
	except (OSError, ValueError):
		return 0.0