"""Resolve provider assignments from host and provider capabilities."""
from __future__ import annotations

from okstra_ctl.domain.host import (
    HostCapabilityMismatch,
    HostDescriptor,
    ProviderUnavailable,
)
from okstra_ctl.domain.provider import (
    LeadProviderAssignment,
    ProviderSpec,
    RunnerResolution,
)


NATIVE_SESSION = "native-session"
CLI_WRAPPER = "cli-wrapper"
RUNNER_VALUES = frozenset({NATIVE_SESSION, CLI_WRAPPER})


def resolve_assignment_runner(
    *,
    host: HostDescriptor,
    provider: ProviderSpec,
    role: str,
    requested_runner: str = "",
) -> RunnerResolution:
    if not provider.supports_role(role):
        raise ProviderUnavailable(
            f"provider {provider.provider!r} does not support role {role!r}"
        )
    requested = (requested_runner or "").strip()
    if requested and requested not in RUNNER_VALUES:
        allowed = ", ".join(sorted(RUNNER_VALUES))
        raise HostCapabilityMismatch(
            f"unknown runner {requested!r}. Allowed values: {allowed}"
        )
    is_native = provider.provider == host.native_provider_id
    if requested == NATIVE_SESSION and not is_native:
        raise HostCapabilityMismatch(
            f"host {host.id!r} does not provide native "
            f"{provider.provider!r} execution"
        )
    if requested == NATIVE_SESSION or (not requested and is_native):
        return RunnerResolution(
            NATIVE_SESSION,
            "",
            f"{host.id} provides native {provider.provider} execution",
        )
    if not provider.wrapper:
        raise ProviderUnavailable(
            f"provider {provider.provider!r} has no CLI wrapper for role {role!r}"
        )
    return RunnerResolution(
        CLI_WRAPPER,
        provider.wrapper,
        f"{host.id} uses the registered {provider.provider} CLI wrapper",
    )


def resolve_lead_provider(
    *,
    host_id: str,
    requested_provider: str,
    host_registry,
    provider_registry,
) -> LeadProviderAssignment:
    host = host_registry.resolve(host_id).descriptor
    provider_id = resolve_lead_provider_id(
        host=host,
        requested_provider=requested_provider,
    )
    provider = provider_registry.resolve(provider_id)
    return resolve_lead_provider_from_specs(
        host=host,
        requested_provider=requested_provider,
        provider=provider,
    )


def resolve_lead_provider_from_specs(
    *,
    host: HostDescriptor,
    requested_provider: str,
    provider: ProviderSpec,
) -> LeadProviderAssignment:
    """Resolve a lead from already loaded host and provider facts."""
    requested = (requested_provider or "").strip().lower()
    _validate_lead_provider_request(host, requested)
    provider_id = requested or host.native_provider_id
    if provider.provider != provider_id:
        raise HostCapabilityMismatch(
            f"provider fact {provider.provider!r} does not match requested "
            f"lead provider {provider_id!r}"
        )
    resolution = resolve_assignment_runner(
        host=host,
        provider=provider,
        role="lead",
    )
    return LeadProviderAssignment(
        provider=provider.provider,
        runner=resolution.runner,
        wrapper=resolution.wrapper,
        reason=resolution.reason,
    )


def resolve_lead_provider_id(
    *,
    host: HostDescriptor,
    requested_provider: str,
) -> str:
    """Validate lead-provider selection and return its canonical id."""
    requested = (requested_provider or "").strip().lower()
    _validate_lead_provider_request(host, requested)
    return requested or host.native_provider_id


def _validate_lead_provider_request(
    host: HostDescriptor,
    requested: str,
) -> None:
    if not requested and not host.native_provider_id:
        raise HostCapabilityMismatch(
            f"host {host.id!r} has no native lead provider; select an explicit "
            "provider with lead capability"
        )
    if host.native_provider_id and requested not in {"", host.native_provider_id}:
        raise HostCapabilityMismatch(
            f"{host.agent_label.removesuffix(' CLI')} host requires provider "
            f"{host.native_provider_id!r} for the in-session lead; provider "
            f"{requested!r} must be selected as a worker instead"
        )
