"""Forms → internal.http_forward. Not a generic HTTP/webhook capability.

Builds a Cloud form.forward-shaped request from trusted composition config.
Does not own fetch, SSRF at the Worker, or webhook transport.
"""
from __future__ import annotations

import ipaddress
from collections.abc import Mapping
from typing import Any
from urllib.parse import urlparse

from pydantic import BaseModel, ConfigDict, StrictBool, StrictStr

from src.domain.forms.actions import FormPostSubmitAction
from src.domain.forms.canonical import (
    STRING_FIELD_TYPES,
    FormDefinition,
    FormFileRef,
    parse_form_file_ref,
)

INTERNAL_FORWARD_ACTION = "internal.http_forward"
CLOUD_REQUEST_TYPE = "form.forward"
FORWARD_METHOD = "POST"
FILE_POLICY = "disallow"
FORWARD_VALUE_FIELD_TYPES: frozenset[str] = STRING_FIELD_TYPES | {"number", "checkbox"}

# Cloud handleFormForward fetch() has no private-network / redirect guards.
SSRF_TRANSPORT = "unprotected_cloud_fetch_documented_gap"

CONTROL_TARGETS: frozenset[str] = frozenset(
    {
        "url",
        "target",
        "target_url",
        "method",
        "headers",
        "authorization",
        "auth",
        "cookie",
        "host",
        "workflow",
        "provider_hint",
        "binding",
        "connection_id",
    }
)
ALLOWED_FORWARD_CONSTANTS: frozenset[str] = frozenset({"target"})

FORMS_HTTP_FORWARD_COMPOSITION: dict[str, Any] = {
    "slice_id": "7k_automation_webhook_composition",
    "status": "frozen",
    "action": INTERNAL_FORWARD_ACTION,
    "not_canonical_capability": True,
    "cloud_request_type": CLOUD_REQUEST_TYPE,
    "method": FORWARD_METHOD,
    "target_source": "trusted_composition_config",
    "routing_from_form_values": False,
    "file_policy": FILE_POLICY,
    "ssrf_transport": SSRF_TRANSPORT,
    "secrets_from_form_values": False,
    "response_into_forms": False,
    "public_bff": False,
    "form_submit_capability": False,
    "codegen": False,
    "docs": "docs/audits/FORMS_AUTOMATION_WEBHOOK_COMPOSITION_7K.md",
}


class HttpForwardBuild(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)
    skipped: StrictBool
    payload: dict[str, Any] | None = None
    error_code: StrictStr | None = None


def http_forward_action(
    *,
    key: str = "forward",
    target: str,
    mapping: Mapping[str, str] | None = None,
    optional: bool = False,
) -> FormPostSubmitAction:
    return FormPostSubmitAction(
        key=key,
        action_type=INTERNAL_FORWARD_ACTION,
        mapping=dict(mapping or {}),
        constants={"target": target},
        optional=optional,
    )


def _source_field(definition: FormDefinition, key: str):
    for field in definition.fields:
        if field.key == key:
            return field
    return None


def _host_blocked(host: str) -> bool:
    name = host.strip("[]").lower().rstrip(".")
    if name in {"localhost", "metadata.google.internal"}:
        return True
    try:
        ip = ipaddress.ip_address(name)
    except ValueError:
        return False
    return bool(
        ip.is_private
        or ip.is_loopback
        or ip.is_link_local
        or ip.is_multicast
        or ip.is_reserved
        or ip.is_unspecified
    )


def validate_forward_target(url: str) -> str | None:
    """Shape + literal-host guard. Not Cloud fetch SSRF and not DNS resolution."""
    if not isinstance(url, str) or not url.strip():
        return "forward_target_invalid"
    parsed = urlparse(url.strip())
    if parsed.scheme not in {"http", "https"}:
        return "forward_target_invalid"
    if parsed.username or parsed.password:
        return "forward_target_invalid"
    host = parsed.hostname
    if not host or _host_blocked(host):
        return "forward_target_invalid"
    return None


def validate_http_forward_mapping(
    action: FormPostSubmitAction,
    definition: FormDefinition,
) -> tuple[str, ...]:
    if action.action_type != INTERNAL_FORWARD_ACTION or action.provider_specific:
        return ()
    errors: list[str] = []
    seen: set[str] = set()
    for target, source in action.mapping.items():
        if "." in target or "[" in target:
            errors.append("nested_forward_target")
            continue
        if target in CONTROL_TARGETS:
            errors.append("routing_from_form_values")
            continue
        if target in seen:
            errors.append("duplicate_forward_target")
            continue
        seen.add(target)
        field = _source_field(definition, source)
        if field is None:
            errors.append("unknown_source_field")
            continue
        if field.type == "file":
            errors.append("unsupported_forward_file_mapping")
            continue
        if field.type not in FORWARD_VALUE_FIELD_TYPES:
            errors.append("unsupported_forward_source_type")
    for key, value in action.constants.items():
        if key not in ALLOWED_FORWARD_CONSTANTS:
            errors.append("unexpected_forward_constant")
            continue
        if key == "target":
            if not isinstance(value, str) or validate_forward_target(value):
                errors.append("forward_target_invalid")
    if "target" not in action.constants:
        errors.append("missing_forward_target")
    return tuple(dict.fromkeys(errors))


def _is_file_value(value: Any) -> bool:
    if isinstance(value, FormFileRef):
        return True
    return parse_form_file_ref(value) is not None


def build_http_forward_payload(
    action: FormPostSubmitAction,
    values: Mapping[str, Any],
) -> HttpForwardBuild:
    """Cloud form.forward shape: trusted target + explicit fields. POST JSON."""
    fields: dict[str, Any] = {}
    for target, source in action.mapping.items():
        if target in CONTROL_TARGETS:
            return HttpForwardBuild(skipped=False, error_code="routing_from_form_values")
        if source not in values:
            continue
        value = values[source]
        if _is_file_value(value):
            return HttpForwardBuild(
                skipped=False,
                error_code="unsupported_forward_file_mapping",
            )
        fields[target] = value
    raw_target = action.constants.get("target")
    if not isinstance(raw_target, str):
        return HttpForwardBuild(skipped=False, error_code="missing_forward_target")
    invalid = validate_forward_target(raw_target)
    if invalid:
        return HttpForwardBuild(skipped=False, error_code=invalid)
    return HttpForwardBuild(
        skipped=False,
        payload={
            "target_url": raw_target.strip(),
            "method": FORWARD_METHOD,
            "fields": fields,
        },
    )
