"""
Authentication settings loaded from ~/.pygitweb/auth.json (or PYGITWEB_AUTH_CONFIG).

On first run with auth enabled, creates the file with generated local credentials.
"""

from __future__ import annotations

import json
import os
import secrets
import stat
import sys
from pathlib import Path
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator

from pygitweb.config import Settings

DEFAULT_AUTH_CONFIG_PATH = Path.home() / ".pygitweb" / "auth.json"
_AUTH_CONFIG_MODE = 0o600


def _is_windows() -> bool:
	return sys.platform == "win32"


def _auth_config_mode(path: Path) -> int:
	return stat.S_IMODE(path.stat().st_mode)


def _set_auth_config_mode(path: Path) -> None:
	if _is_windows():
		return
	os.chmod(path, _AUTH_CONFIG_MODE)


class AuthConfigPermissionError(RuntimeError):
	"""Auth config file exists but is not mode 0600 (non-Windows only)."""


def _require_auth_config_mode(path: Path) -> None:
	if _is_windows():
		return
	mode = _auth_config_mode(path)
	if mode != _AUTH_CONFIG_MODE:
		raise AuthConfigPermissionError(
			f"PyGitWeb: auth config {path} must have mode 0600 (found {mode:#04o}). Run: chmod 600 {path}"
		)


AuthMode = Literal["oauth", "local", "both"]
OAuthUsernameFrom = Literal["sub", "email", "preffered_username", "login", "name"]


class LocalUser(BaseModel):
	model_config = ConfigDict(populate_by_name=True)

	user: str = Field(min_length=2)
	password: str = Field(default="", alias="pass", serialization_alias="pass")


class AuthConfig(BaseModel):
	model_config = ConfigDict(populate_by_name=True)

	auth_mode: AuthMode = "local"
	oauth_client_secret: str = ""
	oauth_redirect_uri: str = ""
	oauth_username_from: OAuthUsernameFrom = "email"
	oauth_allowed_emails: list[str] = Field(default_factory=list)
	oauth_permissions: dict[str, list[str]] = Field(default_factory=lambda: {"*": []})
	local_users: list[LocalUser] = Field(default_factory=list)

	@field_validator("oauth_permissions", mode="before")
	@classmethod
	def _ensure_wildcard_permissions(cls, v: object) -> object:
		if v is None:
			return {"*": []}
		if isinstance(v, dict) and "*" not in v:
			return {**v, "*": []}
		return v


def auth_config_path(settings: Settings) -> Path:
	if settings.AUTH_CONFIG:
		return Path(settings.AUTH_CONFIG).expanduser()
	return DEFAULT_AUTH_CONFIG_PATH


def default_auth_config(*, admin_password: str) -> AuthConfig:
	return AuthConfig(
		auth_mode="local",
		oauth_client_secret="",
		oauth_redirect_uri="",
		oauth_username_from="email",
		oauth_allowed_emails=[],
		oauth_permissions={"*": []},
		local_users=[LocalUser(user="admin", password=admin_password)],
	)


def write_auth_config(path: Path, config: AuthConfig) -> None:
	path.parent.mkdir(parents=True, exist_ok=True)
	path.write_text(
		json.dumps(config.model_dump(by_alias=True, mode="json"), indent=2) + "\n",
		encoding="utf-8",
	)
	_set_auth_config_mode(path)


def load_auth_config_file(path: Path) -> AuthConfig:
	return AuthConfig.model_validate_json(path.read_text(encoding="utf-8"))


def is_local_auth_available(config: AuthConfig) -> bool:
	if config.auth_mode not in ("local", "both"):
		return False
	return any(u.password for u in config.local_users)


def is_oauth_auth_available(config: AuthConfig) -> bool:
	if config.auth_mode not in ("oauth", "both"):
		return False
	return bool(config.oauth_client_secret.strip() and config.oauth_redirect_uri.strip())


def is_auth_configured(config: AuthConfig) -> bool:
	if config.auth_mode == "local":
		return is_local_auth_available(config)
	if config.auth_mode == "oauth":
		return is_oauth_auth_available(config)
	return is_local_auth_available(config) or is_oauth_auth_available(config)


def init_auth_config(settings: Settings) -> AuthConfig:
	global auth_config
	path = auth_config_path(settings)
	if path.is_file():
		if settings.AUTH:
			_require_auth_config_mode(path)
		auth_config = load_auth_config_file(path)
		return auth_config
	if not settings.AUTH:
		auth_config = AuthConfig(auth_mode="local", local_users=[])
		return auth_config
	password = secrets.token_urlsafe(16)
	auth_config = default_auth_config(admin_password=password)
	write_auth_config(path, auth_config)
	print(
		f"WARNING: PyGitWeb: created auth config at {path}\n"
		"         Set PYGITWEB_AUTH=0 to disable auth, or edit this file for fixed credentials.\n",
		file=sys.stderr,
	)
	print(f"  Username: admin\n  Password: {password}\n", file=sys.stderr)
	return auth_config


auth_config: AuthConfig = AuthConfig(auth_mode="local", local_users=[])