"""Strongly-typed Bitbucket settings using pydantic-settings (consistent with VDS orchestrators)."""

from __future__ import annotations

from collections.abc import Mapping
from pathlib import Path
from typing import Any

from pydantic import AliasChoices, Field, ValidationError
from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
)
from vds_cli_common.env import DEFAULT_ENV_PATHS, load_shared_env
from vds_platform_core.config import (
    VDS_BASE_SETTINGS_CONFIG,
    HTTPClientSettings,
    vds_settings_customise_sources,
)
from vds_platform_core.errors import SettingsError


class BitbucketSettings(HTTPClientSettings, BaseSettings):
    model_config = SettingsConfigDict(**VDS_BASE_SETTINGS_CONFIG, env_prefix="BITBUCKET_")

    # Override HTTPClientSettings retry default to match bitbucket's legacy
    # behavior (1.0s initial backoff vs platform_core's 0.5s default); alias
    # preserves the BITBUCKET_RETRY_BACKOFF env var contract.
    retry_backoff_initial: float = Field(default=1.0, alias="BITBUCKET_RETRY_BACKOFF", ge=0)

    # Backward-compat alias for code consumers (client.py uses retry_backoff_factor)
    @property
    def retry_backoff_factor(self) -> float:
        """Legacy alias for retry_backoff_initial."""
        return self.retry_backoff_initial

    username: str | None = Field(default=None, alias="VDS_USERNAME")
    password: str | None = Field(default=None, alias="VDS_PASSWORD")

    # Bitbucket Server/DC URL (for on-premise installations)
    # Primary: VDS_BITBUCKET_URL (FR-21.1.1)
    # Backward-compatible aliases: BITBUCKET_BASE_URL, VDS_BITBUCKET_HTTP_BASE
    # Priority: VDS_BITBUCKET_URL > BITBUCKET_BASE_URL > VDS_BITBUCKET_HTTP_BASE
    base_url: str = Field(
        validation_alias=AliasChoices(
            "VDS_BITBUCKET_URL",  # Primary (highest priority)
            "BITBUCKET_BASE_URL",  # Legacy alias
            "VDS_BITBUCKET_HTTP_BASE",  # git_orchestrator alias
        ),
    )

    # Bitbucket Cloud workspace (for cloud.bitbucket.com)
    cloud_workspace: str | None = Field(default=None, alias="BITBUCKET_CLOUD_WORKSPACE")

    # Optional: Personal Access Token for Bitbucket Cloud or Server
    token: str | None = Field(default=None, alias="BITBUCKET_TOKEN")

    # Default mode: 'cloud' or 'server'
    mode: str = Field(default="server", alias="BITBUCKET_MODE")

    # Note: max_retries is inherited from HTTPClientSettings with env_prefix
    # BITBUCKET_ → binds to BITBUCKET_MAX_RETRIES. retry_backoff_initial is
    # declared above with explicit BITBUCKET_RETRY_BACKOFF alias override.

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """Delegate to platform_core helper with local DEFAULT_ENV_PATHS."""
        return vds_settings_customise_sources(
            settings_cls,
            init_settings,
            env_settings,
            dotenv_settings,
            file_secret_settings,
            env_paths=DEFAULT_ENV_PATHS,
        )

    def require_basic_or_token(self) -> None:
        if not ((self.username and self.password) or self.token):
            raise ValueError("Missing credentials: set VDS_USERNAME/VDS_PASSWORD or BITBUCKET_TOKEN")

    @property
    def is_cloud_mode(self) -> bool:
        return self.mode.lower() == "cloud"

    @property
    def is_server_mode(self) -> bool:
        return self.mode.lower() == "server"

    def get_url(self) -> str:
        """Get appropriate URL based on mode."""
        if self.is_cloud_mode:
            if not self.cloud_workspace:
                raise ValueError("BITBUCKET_CLOUD_WORKSPACE is required for cloud mode")
            return f"https://api.bitbucket.org/workspace/{self.cloud_workspace}"
        return str(self.base_url)

    def get_auth_kwargs(self) -> dict[str, str]:
        """Get authentication kwargs for atlassian-python-api."""
        if self.token:
            return {"token": self.token}
        elif self.username and self.password:
            return {"username": self.username, "password": self.password}
        else:
            raise ValueError("No valid authentication credentials available")


def load_settings(
    *,
    env_path: Path | None = None,
    overrides: Mapping[str, Any] | None = None,
    strict: bool = False,
) -> BitbucketSettings:
    """Load orchestrator settings using native pydantic-settings.

    Args:
        env_path: Optional path to a specific .env file (for testing).
                 When provided, uses custom settings source instead of DEFAULT_ENV_PATHS.
        overrides: Optional mapping of values to override settings.
        strict: If True, require valid credentials (basic auth or token).

    Returns:
        BitbucketSettings instance with loaded configuration.

    Raises:
        SettingsError: If validation fails or strict mode requirements not met.
    """
    load_shared_env(path=env_path)
    try:
        kwargs: dict[str, Any] = dict(overrides) if overrides else {}

        if env_path is not None:
            from pydantic_settings.sources import DotEnvSettingsSource

            class CustomBitbucketSettings(BitbucketSettings):
                @classmethod
                def settings_customise_sources(
                    cls,
                    settings_cls: type[BaseSettings],
                    init_settings: PydanticBaseSettingsSource,
                    env_settings: PydanticBaseSettingsSource,
                    dotenv_settings: PydanticBaseSettingsSource,
                    file_secret_settings: PydanticBaseSettingsSource,
                ) -> tuple[PydanticBaseSettingsSource, ...]:
                    """Custom source chain: init → custom env_path → env vars.

                    env_path precedes env_settings so explicit test/override
                    files beat ambient runtime env (preserves legacy
                    load_shared_env(override=True) semantics).
                    """
                    sources: list[PydanticBaseSettingsSource] = [init_settings]
                    if env_path.exists():
                        sources.append(
                            DotEnvSettingsSource(
                                settings_cls,
                                env_file=env_path,
                                env_file_encoding="utf-8",
                            )
                        )
                    sources.append(env_settings)
                    return tuple(sources)

            settings = CustomBitbucketSettings(**kwargs)
        else:
            settings = BitbucketSettings(**kwargs)

    except ValidationError as exc:
        raise SettingsError(str(exc)) from exc

    if strict:
        # Let ValueError propagate unwrapped (matches legacy contract; tests
        # use pytest.raises(ValueError, match="Missing credentials"))
        settings.require_basic_or_token()

    return settings


__all__ = ["DEFAULT_ENV_PATHS", "BitbucketSettings", "SettingsError", "load_settings"]
