from __future__ import annotations

"""Helpers for merging incoming user-configuration updates with what is already
stored, while honouring masked API keys.
"""

import copy
from typing import Dict

from api.schemas.ai_model_configuration import EffectiveAIModelConfiguration
from api.services.configuration.masking import (
    MODEL_OVERRIDE_FIELDS,
    SERVICE_SECRET_FIELDS,
    contains_masked_key,
    resolve_masked_api_keys,
)

SERVICE_FIELDS = ("llm", "tts", "stt", "embeddings", "realtime")


def _same_provider(incoming_cfg: dict, existing_cfg: dict) -> bool:
    return not (
        existing_cfg.get("provider") is not None
        and incoming_cfg.get("provider") is not None
        and incoming_cfg.get("provider") != existing_cfg.get("provider")
    )


def _merge_service_secret_fields(
    incoming_cfg: dict,
    existing_cfg: dict,
    *,
    preserve_missing: bool,
    masked_value_preserves_full_secret: bool = False,
) -> dict:
    """Restore existing real secrets when incoming values are masked.

    If ``preserve_missing`` is true, missing incoming secret fields are also
    copied from the existing config. User config updates need that behavior;
    workflow model overrides leave missing secrets blank so later enrichment can
    copy from the current global config.
    """
    if not _same_provider(incoming_cfg, existing_cfg):
        return incoming_cfg

    for secret_field in SERVICE_SECRET_FIELDS:
        if secret_field not in existing_cfg:
            continue

        incoming_secret = incoming_cfg.get(secret_field)
        existing_secret = existing_cfg[secret_field]
        if incoming_secret is not None:
            if contains_masked_key(incoming_secret):
                incoming_cfg[secret_field] = (
                    existing_secret
                    if masked_value_preserves_full_secret
                    else resolve_masked_api_keys(
                        incoming_secret,
                        existing_secret,
                    )
                )
        elif preserve_missing:
            incoming_cfg[secret_field] = existing_secret

    return incoming_cfg


def merge_user_configurations(
    existing: EffectiveAIModelConfiguration, incoming_partial: Dict[str, dict]
) -> EffectiveAIModelConfiguration:
    """Merge *incoming_partial* onto *existing* and return a new EffectiveAIModelConfiguration.

    *incoming_partial* is the body of the PUT request (already `model_dump()`ed or
    extracted via Pydantic `model_dump`).

    Rules:
    1. If a service block is absent in the request, keep the existing one.
    2. If provider unchanged and the api_key field is either missing or equal to
       the masked placeholder, preserve the existing real key.
    3. If provider changes, the incoming api_key is used verbatim (validation
       will fail later if it is missing).
    4. Non-service top-level fields (e.g. `test_phone_number`) are overwritten
       when supplied.
    """

    merged = existing.model_dump(exclude_none=True)

    def _merge_service_block(service_name: str):
        incoming_cfg = incoming_partial.get(service_name)
        if incoming_cfg is None:
            return  # nothing to do

        old_cfg = merged.get(service_name, {})
        if old_cfg:
            incoming_cfg = _merge_service_secret_fields(
                incoming_cfg,
                old_cfg,
                preserve_missing=True,
            )

        merged[service_name] = incoming_cfg

    for service in SERVICE_FIELDS:
        _merge_service_block(service)

    # other simple fields
    if "is_realtime" in incoming_partial:
        merged["is_realtime"] = incoming_partial["is_realtime"]

    if "test_phone_number" in incoming_partial:
        merged["test_phone_number"] = incoming_partial["test_phone_number"]

    if "timezone" in incoming_partial:
        merged["timezone"] = incoming_partial["timezone"]

    return EffectiveAIModelConfiguration.model_validate(merged)


def merge_workflow_configuration_secrets(
    incoming_config: dict | None,
    existing_config: dict | None,
) -> dict | None:
    """Restore persisted workflow override secrets when the client sends masks.

    Workflow model overrides intentionally persist real keys so a workflow keeps
    running after the global provider changes. API responses mask those keys, so
    save requests must merge masked placeholders back to the stored real values.

    Unlike user config updates, a missing workflow override secret is not copied
    from the existing workflow config. Missing means "copy from current global"
    during the later enrichment step.
    """
    if not incoming_config or not existing_config:
        return incoming_config

    merged = copy.deepcopy(incoming_config)
    incoming_overrides = merged.get("model_overrides")
    existing_overrides = existing_config.get("model_overrides")
    if not isinstance(incoming_overrides, dict) or not isinstance(
        existing_overrides, dict
    ):
        return merged

    for section in MODEL_OVERRIDE_FIELDS:
        incoming_section = incoming_overrides.get(section)
        existing_section = existing_overrides.get(section)
        if not isinstance(incoming_section, dict) or not isinstance(
            existing_section, dict
        ):
            continue

        incoming_overrides[section] = _merge_service_secret_fields(
            incoming_section,
            existing_section,
            preserve_missing=False,
            masked_value_preserves_full_secret=True,
        )

    return merged
