"""Top-level validate() and validate_institutional() convenience functions."""

from __future__ import annotations

import time
from typing import Any

from claim_validator.conf import ClaimValidatorSettings
from claim_validator.models.claim import ClaimData
from claim_validator.models.institutional import InstitutionalClaimData
from claim_validator.models.results import (
    PhaseResult,
    PipelineResult,
    ValidatorOutput,
)
from claim_validator.validators.pipeline import ValidationPipeline
from claim_validator.validators.registry import ValidatorRegistry


def validate(
    claim: dict[str, Any] | ClaimData,
    *,
    settings: ClaimValidatorSettings | None = None,
    ai_config: dict[str, Any] | None = None,
    clearinghouse_config: dict[str, Any] | None = None,
) -> PipelineResult:
    """Validate a healthcare claim and return structured results.

    Args:
        claim: Claim data as a dict or ClaimData instance.
        settings: Optional settings. Uses defaults if None
            (all 8 rule-based validators, no AI).
        ai_config: Optional AI provider config dict.
            Keys: provider, api_key, model (+ provider-specific).
            Overrides settings.ai_config if both provided.
        clearinghouse_config: Optional clearinghouse provider config dict.
            Keys: ``provider`` (required), plus provider-specific keys
            (``api_key``, ``secret``, etc.). Overrides
            ``settings.clearinghouse_config`` when both provided.

    Returns:
        PipelineResult with findings from all validators.

    Raises:
        pydantic.ValidationError: If claim dict is malformed.
    """
    if isinstance(claim, dict):
        claim_data = ClaimData(**claim)
    elif isinstance(claim, ClaimData):
        claim_data = claim
    else:
        msg = (
            f"claim must be a dict or ClaimData, "
            f"got {type(claim).__name__}"
        )
        raise ValueError(msg)

    if ai_config is not None:
        if settings is not None:
            settings = settings.model_copy(
                update={"ai_config": ai_config},
            )
        else:
            settings = ClaimValidatorSettings(
                ai_config=ai_config,
            )

    if clearinghouse_config is not None:
        if settings is not None:
            settings = settings.model_copy(
                update={"clearinghouse_config": clearinghouse_config},
            )
        else:
            settings = ClaimValidatorSettings(
                clearinghouse_config=clearinghouse_config,
            )

    pipeline = ValidationPipeline.from_settings(settings)
    return pipeline.run(claim_data)


async def validate_async(
    claim: dict[str, Any] | ClaimData,
    *,
    settings: ClaimValidatorSettings | None = None,
    ai_config: dict[str, Any] | None = None,
) -> PipelineResult:
    """Async version of validate() — runs rule-based validators in a thread pool.

    Rule-based validators are CPU-bound and synchronous. This wraps them in
    asyncio.to_thread() so they do not block the event loop. AI validators
    are not supported in this function (use validate() for AI).

    Args:
        claim: Claim data as a dict or ClaimData instance.
        settings: Optional settings. Uses defaults if None.
        ai_config: Ignored (async does not support AI validators).

    Returns:
        PipelineResult with findings from rule-based validators.
    """
    import asyncio

    return await asyncio.to_thread(validate, claim, settings=settings)


async def validate_institutional_async(
    claim: dict[str, Any] | InstitutionalClaimData,
    *,
    settings: ClaimValidatorSettings | None = None,
) -> PipelineResult:
    """Async version of validate_institutional() — runs validators in a thread pool.

    Args:
        claim: Claim data as a dict or InstitutionalClaimData instance.
        settings: Optional settings. Uses defaults if None.

    Returns:
        PipelineResult with findings from all validators.
    """
    import asyncio

    return await asyncio.to_thread(
        validate_institutional, claim, settings=settings,
    )


def validate_batch(
    claims: list[dict[str, Any] | ClaimData],
    *,
    settings: ClaimValidatorSettings | None = None,
    stop_on_first_error: bool = False,
) -> list[PipelineResult]:
    """Validate a list of claims and return one PipelineResult per claim.

    Args:
        claims: List of claim dicts or ClaimData instances.
        settings: Optional shared settings for all claims.
        stop_on_first_error: If True, stop processing after the first
            claim that has ERROR-severity findings.

    Returns:
        List of PipelineResult, one per input claim (in order).
    """
    from claim_validator.constants import Severity

    results: list[PipelineResult] = []
    for claim in claims:
        result = validate(claim, settings=settings)
        results.append(result)
        if stop_on_first_error and any(
            finding.severity == Severity.ERROR
            for pr in result.phase_results
            for vo in pr.validator_outputs
            for finding in vo.findings
        ):
            break
    return results


async def validate_batch_async(
    claims: list[dict[str, Any] | ClaimData],
    *,
    settings: ClaimValidatorSettings | None = None,
    max_concurrency: int = 10,
) -> list[PipelineResult]:
    """Async batch validation with bounded concurrency via asyncio.Semaphore.

    Args:
        claims: List of claim dicts or ClaimData instances.
        settings: Optional shared settings for all claims.
        max_concurrency: Maximum number of concurrent validations.

    Returns:
        List of PipelineResult in the same order as input claims.
    """
    import asyncio

    semaphore = asyncio.Semaphore(max_concurrency)

    async def _validate_one(claim: dict[str, Any] | ClaimData) -> PipelineResult:
        async with semaphore:
            return await validate_async(claim, settings=settings)

    return list(await asyncio.gather(*[_validate_one(c) for c in claims]))


def validate_institutional(
    claim: dict[str, Any] | InstitutionalClaimData,
    *,
    settings: ClaimValidatorSettings | None = None,
) -> PipelineResult:
    """Validate a UB-04 institutional healthcare claim.

    Args:
        claim: Claim data as a dict or InstitutionalClaimData instance.
        settings: Optional settings. Uses defaults if None
            (all 4 institutional validators).

    Returns:
        PipelineResult with findings from all validators.

    Raises:
        pydantic.ValidationError: If claim dict is malformed.
    """
    if isinstance(claim, dict):
        claim_data = InstitutionalClaimData(**claim)
    elif isinstance(claim, InstitutionalClaimData):
        claim_data = claim
    else:
        msg = (
            f"claim must be a dict or InstitutionalClaimData, "
            f"got {type(claim).__name__}"
        )
        raise ValueError(msg)

    if settings is None:
        settings = ClaimValidatorSettings()

    registry = ValidatorRegistry()
    validators = registry.create_validators(
        settings.institutional_rule_validators,
    )

    start = time.perf_counter()
    outputs: list[ValidatorOutput] = []
    for validator in validators:
        output = validator.validate(claim_data)
        outputs.append(output)

    elapsed = time.perf_counter() - start
    return PipelineResult(
        phase_results=[
            PhaseResult(
                phase="rule_based",
                validator_outputs=outputs,
                execution_time=elapsed,
            ),
        ],
        execution_time=elapsed,
    )
