"""8C: Core/Cloud catalog parity, source placeholders, replace-all overlay, enabled semantics."""
from __future__ import annotations

import html
import json
import re
from pathlib import Path

import pytest
from pydantic import ValidationError

from src.domain.codegen.email_templates_manifest import (
    EMAIL_TEMPLATE_CATALOG,
    OVERLAY_SYNC_CONTRACT,
    EmailTemplateDeclaration,
    apply_email_templates,
    load_manifest_from_repo_path,
    validate_manifest,
)
from src.domain.connectors.invoke_capabilities import CAPABILITY_CONVERSION_PATTERN, FORMS_FAMILY_PLAN
from src.domain.connectors.schemas import InvokeEmailSendRequest
from src.domain.forms.email import FORBIDDEN_EMAIL_CONSTANT_KEYS, FORMS_EMAIL_COMPOSITION
from src.domain.manifest.schemas import ManifestEmailTemplatesSection
from tests.email_catalog_test_support import load_cloud_catalog_meta

BE_ROOT = Path(__file__).resolve().parents[1]
CLOUD_RENDER = BE_ROOT.parent / "P2HSCloud" / "apps" / "pc-gateway" / "src" / "lib" / "email-templates.ts"
CLOUD_VALIDATE = BE_ROOT.parent / "P2HSCloud" / "apps" / "pc-gateway" / "src" / "lib" / "email-template-validate.ts"
CORE_META = BE_ROOT / "src" / "domain" / "codegen" / "email_template_catalog.meta.json"

CLOSED_KEYS = (
    "auth.verify_email",
    "auth.otp_request",
    "auth.magic_link",
    "auth.password_reset",
    "auth.invite",
    "contact_capture.admin_notify",
    "contact_capture.confirmation",
)


def substitute_text(template: str, vars_map: dict[str, str]) -> str:
    def repl(match: re.Match[str]) -> str:
        name = match.group(1)
        return vars_map[name] if name in vars_map else match.group(0)

    return re.sub(r"\{\{\s*([a-z_]+)\s*\}\}", repl, template, flags=re.I)


def substitute_html(template: str, vars_map: dict[str, str]) -> str:
    def repl(match: re.Match[str]) -> str:
        name = match.group(1)
        return html.escape(vars_map[name], quote=True) if name in vars_map else match.group(0)

    return re.sub(r"\{\{\s*([a-z_]+)\s*\}\}", repl, template, flags=re.I)


def source_ok(text_body: str, html_body: str | None, required: list[str]) -> bool:
    if any(token not in text_body for token in required):
        return False
    if html_body is not None and any(token not in html_body for token in required):
        return False
    return True


def render_contract(
    *,
    optional: bool,
    required: list[str],
    default: dict[str, str],
    override: dict | None,
    vars_map: dict[str, str],
) -> dict:
    if override is None:
        return {**default, "used_default": True}
    if not override.get("enabled", True) and optional:
        return {**default, "used_default": True}
    if not source_ok(override["text_body"], override.get("html_body"), required):
        return {**default, "used_default": True}
    html_src = override.get("html_body")
    return {
        "subject": substitute_text(override["subject"], vars_map),
        "text": substitute_text(override["text_body"], vars_map),
        "html": substitute_html(html_src, vars_map) if html_src else "",
        "used_default": False,
    }


def test_overlay_sync_contract_is_replace_all() -> None:
    assert OVERLAY_SYNC_CONTRACT == "replace_all"
    guidance = ManifestEmailTemplatesSection().pre_generation_question
    assert "replace-all" in guidance
    assert "keep their current" not in guidance
    assert "does not skip sending" in guidance or "does not skip" in guidance


def test_core_cloud_catalog_metadata_parity() -> None:
    assert tuple(EMAIL_TEMPLATE_CATALOG) == CLOSED_KEYS
    core = json.loads(CORE_META.read_text(encoding="utf-8"))
    assert tuple(core) == CLOSED_KEYS
    cloud = load_cloud_catalog_meta()
    assert tuple(cloud) == CLOSED_KEYS
    for key in CLOSED_KEYS:
        assert EMAIL_TEMPLATE_CATALOG[key]["optional"] == cloud[key]["optional"] == core[key]["optional"]
        assert cloud[key]["archetype"] == core[key]["archetype"]
        assert cloud[key]["defaultProfile"] == core[key]["default_profile"]
        assert (
            EMAIL_TEMPLATE_CATALOG[key]["protected_placeholders"]
            == cloud[key]["requiredPlaceholders"]
            == core[key]["protected_placeholders"]
        )
        assert (
            list(EMAIL_TEMPLATE_CATALOG[key]["required_variables"])
            == cloud[key]["requiredVariables"]
            == core[key]["required_variables"]
        )
        assert (
            dict(EMAIL_TEMPLATE_CATALOG[key]["optional_variables"])
            == cloud[key]["optionalVariables"]
            == core[key]["optional_variables"]
        )


def test_cloud_validates_placeholders_on_override_source_not_rendered_output() -> None:
    render_src = CLOUD_RENDER.read_text(encoding="utf-8")
    validate_src = CLOUD_VALIDATE.read_text(encoding="utf-8")
    assert "overrideSourcePreservesPlaceholders(override" in render_src
    assert "substituteText(override.subject" in render_src
    assert render_src.index("overrideSourcePreservesPlaceholders(override") < render_src.index(
        "substituteText(override.subject"
    )
    assert "hasRequiredPlaceholders(override.text_body" in validate_src
    assert "RenderedEmail" not in validate_src


def test_protected_placeholder_source_then_successful_render() -> None:
    required = ["{{action_url}}"]
    default = {"subject": "Verify your email", "text": "d", "html": "d"}
    override = {
        "subject": "Go {{action_url}}",
        "text_body": "Go {{action_url}}",
        "html_body": "<a href=\"{{action_url}}\">x</a>",
        "enabled": True,
    }
    rendered = render_contract(
        optional=False,
        required=required,
        default=default,
        override=override,
        vars_map={"action_url": "https://example.com/v", "name": "Ada"},
    )
    assert rendered["used_default"] is False
    assert "{{action_url}}" not in rendered["text"]
    assert rendered["text"] == "Go https://example.com/v"
    assert rendered["subject"] == "Go https://example.com/v"
    EmailTemplateDeclaration(
        template_key="auth.verify_email",
        subject=override["subject"],
        text_body=override["text_body"],
        html_body=override["html_body"],
    )


def test_missing_protected_placeholder_rejected_before_sync() -> None:
    with pytest.raises(ValidationError, match="action_url"):
        validate_manifest(
            {
                "version": 1,
                "templates": [
                    {
                        "template_key": "auth.verify_email",
                        "subject": "Verify",
                        "text_body": "Please verify",
                    }
                ],
            }
        )


def test_unknown_key_rejected() -> None:
    with pytest.raises(ValidationError, match="Unknown template_key"):
        EmailTemplateDeclaration(template_key="invoice.reminder", subject="S", text_body="T")


def test_optional_enabled_false_falls_back_to_default_not_suppression() -> None:
    default = {"subject": "We've received your message", "text": "default-copy", "html": "<p>d</p>"}
    rendered = render_contract(
        optional=True,
        required=[],
        default=default,
        override={"subject": "Custom", "text_body": "Custom body", "enabled": False},
        vars_map={"name": "Ada"},
    )
    assert rendered["used_default"] is True
    assert rendered["text"] == "default-copy"
    assert "template_key" not in rendered


def test_non_optional_enabled_false_still_uses_override() -> None:
    rendered = render_contract(
        optional=False,
        required=["{{code}}"],
        default={"subject": "d", "text": "d", "html": "d"},
        override={
            "subject": "Code",
            "text_body": "Your {{code}}",
            "enabled": False,
        },
        vars_map={"code": "123456"},
    )
    assert rendered["used_default"] is False
    assert rendered["text"] == "Your 123456"


def test_unknown_override_placeholder_rejected_at_source() -> None:
    with pytest.raises(ValidationError, match="unknown template variable"):
        EmailTemplateDeclaration(
            template_key="auth.verify_email",
            subject="Hi {{name}}",
            text_body="Go {{action_url}}",
        )
    with pytest.raises(ValidationError, match="unknown template variable"):
        EmailTemplateDeclaration(
            template_key="auth.verify_email",
            subject="Verify",
            text_body="Go {{action_url}} {{typo_name}}",
        )
    with pytest.raises(ValidationError, match="unknown template variable"):
        EmailTemplateDeclaration(
            template_key="auth.verify_email",
            subject="Verify",
            text_body="Go {{action_url}}",
            html_body="<p>{{typo_name}}</p>{{action_url}}",
        )


def test_html_substitution_escapes_variable_values() -> None:
    rendered = render_contract(
        optional=True,
        required=[],
        default={"subject": "d", "text": "d", "html": "d"},
        override={
            "subject": "S",
            "text_body": "T",
            "html_body": "<p>{{message}}</p>",
            "enabled": True,
        },
        vars_map={"message": "<script>alert(1)</script>"},
    )
    assert rendered["html"] == "<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>"


@pytest.mark.asyncio
async def test_replace_all_push_including_empty_desired_state() -> None:
    class Client:
        def __init__(self) -> None:
            self.calls: list[list] = []

        async def push_email_templates(self, project_id: str, templates: list) -> None:
            self.calls.append(list(templates))

    client = Client()
    await apply_email_templates(client, "prj_1", {"templates": []})
    assert client.calls == [[]]
    await apply_email_templates(
        client,
        "prj_1",
        {
            "templates": [
                {
                    "template_key": "contact_capture.confirmation",
                    "subject": "Thanks",
                    "text_body": "Hi",
                    "enabled": True,
                }
            ]
        },
    )
    assert len(client.calls[1]) == 1
    await apply_email_templates(client, "prj_1", {"templates": []})
    assert client.calls[2] == []


def test_missing_overlay_file_is_none() -> None:
    assert load_manifest_from_repo_path(str(BE_ROOT / "tests")) is None


def test_email_send_and_forms_boundaries_unchanged() -> None:
    assert set(InvokeEmailSendRequest.model_fields) == {
        "to",
        "subject",
        "text",
        "html",
        "reply_to",
        "idempotency_key",
    }
    payload = InvokeEmailSendRequest(to="a@b.co", subject="S", text="T", html="<p>x</p>").model_dump()
    assert "template_key" not in payload
    assert "used_default" not in payload
    assert FORMS_EMAIL_COMPOSITION["email_templates"] is False
    assert FORMS_EMAIL_COMPOSITION["interpolation"] is False
    assert "template_key" in FORBIDDEN_EMAIL_CONSTANT_KEYS


def test_tracker_points_past_8c() -> None:
    assert CAPABILITY_CONVERSION_PATTERN["phase_8c_status"] == "complete"
    assert CAPABILITY_CONVERSION_PATTERN["phase_8d_status"] == "complete"
    assert CAPABILITY_CONVERSION_PATTERN["phase_8e_status"] == "complete"
    assert CAPABILITY_CONVERSION_PATTERN["next_program_slice"] == "9_app_block_modernization"
    assert FORMS_FAMILY_PLAN["next_slice"] == "9_app_block_modernization"
    assert CAPABILITY_CONVERSION_PATTERN["phase_7_status"] == "complete"
