import hashlib
import hmac
import json

from fastapi.testclient import TestClient
from sqlalchemy import select

from services.interaction_service.app import app as interaction_app
from services.shared.db import get_session
from services.shared.sql_models import (
    CustomerExternalIdentity,
    Interaction,
    WhatsAppMessageRow,
    WhatsAppThreadRow,
)
from services.whatsapp_adapter_service import app as whatsapp_module
from services.whatsapp_adapter_service.app import app as whatsapp_app


def admin_headers():
    return {"X-User": "admin", "X-Role": "admin"}


def operator_headers(user="operator"):
    return {"X-User": user, "X-Role": "operator"}


def patch_interaction_request(monkeypatch):
    def fake_request(method: str, path: str, *, payload: dict | None = None) -> dict:
        session = get_session()
        try:
            interaction_id = path.split("/")[2]
            interaction = session.execute(
                select(Interaction).where(Interaction.interaction_id == interaction_id)
            ).scalar_one()
            if path.endswith("/assign"):
                interaction.assigned_to = payload["assignee"]
                interaction.status = "in_progress"
            elif path.endswith("/status"):
                interaction.status = payload["status"]
            elif path.endswith("/escalate"):
                interaction.status = "escalated"
                interaction.queue_id = payload["target_queue_id"]
            interaction.updated_at = whatsapp_module.utc_now_iso()
            session.commit()
            return {
                "interaction_id": interaction.interaction_id,
                "status": interaction.status,
                "assigned_to": interaction.assigned_to,
                "queue_id": interaction.queue_id,
            }
        finally:
            session.close()

    monkeypatch.setattr(whatsapp_module, "_interaction_request", fake_request)


def _meta_signature(secret: str, body: bytes) -> str:
    digest = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
    return f"sha256={digest}"


def test_manual_webhook_creates_thread_and_linked_interaction():
    whatsapp_client = TestClient(whatsapp_app)
    interaction_client = TestClient(interaction_app)

    response = whatsapp_client.post(
        "/integrations/whatsapp/webhook",
        json={
            "chat_id": "wa_chat_1",
            "text": "Привет из WhatsApp",
            "external_message_id": "wa_ext_1",
            "whatsapp_user_id": "wa_user_1",
            "phone_number": "+77015550001",
            "display_name": "Lead One",
            "payload": {"profile_name": "Lead One"},
        },
    )
    assert response.status_code == 200
    payload = response.json()
    assert payload["thread_id"].startswith("wht_")
    assert payload["interaction_id"].startswith("int_")
    assert payload["direction"] == "inbound"
    assert payload["customer_id"].startswith("cus_")

    thread_list = whatsapp_client.get("/integrations/whatsapp/threads", headers=admin_headers())
    assert thread_list.status_code == 200
    listed_thread = next(item for item in thread_list.json() if item["thread_id"] == payload["thread_id"])
    assert listed_thread["chat_id"] == "wa_chat_1"
    assert listed_thread["phone_number"] == "+77015550001"
    assert listed_thread["unread_count"] == 1

    interaction = interaction_client.get(f"/interactions/{payload['interaction_id']}")
    assert interaction.status_code == 200
    assert interaction.json()["channel"] == "whatsapp"
    assert interaction.json()["customer_id"] == payload["customer_id"]

    session = get_session()
    try:
        identity = session.execute(
            select(CustomerExternalIdentity).where(
                CustomerExternalIdentity.channel == "whatsapp",
                CustomerExternalIdentity.external_subject == "wa_user_1",
            )
        ).scalar_one_or_none()
        assert identity is not None
        assert identity.customer_id == payload["customer_id"]
    finally:
        session.close()


def test_manual_webhook_deduplicates_by_external_message_id():
    whatsapp_client = TestClient(whatsapp_app)
    payload = {
        "chat_id": "wa_chat_dedupe",
        "text": "Повторный inbound",
        "external_message_id": "wa_ext_dedupe_1",
        "whatsapp_user_id": "wa_user_dedupe",
        "phone_number": "+77015550002",
        "display_name": "Dedupe Lead",
        "payload": {"provider": "manual"},
    }

    first = whatsapp_client.post("/integrations/whatsapp/webhook", json=payload)
    second = whatsapp_client.post("/integrations/whatsapp/webhook", json=payload)

    assert first.status_code == 200
    assert second.status_code == 200
    assert first.json()["message_id"] == second.json()["message_id"]
    assert first.json()["thread_id"] == second.json()["thread_id"]

    messages = whatsapp_client.get(
        f"/integrations/whatsapp/threads/{first.json()['thread_id']}/messages",
        headers=admin_headers(),
    )
    assert messages.status_code == 200
    assert len(messages.json()) == 1


def test_provider_webhook_accepts_meta_style_payload(monkeypatch):
    monkeypatch.setenv("WHATSAPP_WEBHOOK_SECRET", "wa-secret")
    whatsapp_client = TestClient(whatsapp_app)

    response = whatsapp_client.post(
        "/integrations/whatsapp/provider/webhook",
        headers={"X-WhatsApp-Webhook-Secret": "wa-secret"},
        json={
            "entry": [
                {
                    "changes": [
                        {
                            "value": {
                                "contacts": [{"wa_id": "77015550003", "profile": {"name": "Meta Lead"}}],
                                "messages": [
                                    {
                                        "id": "wam_meta_1",
                                        "from": "77015550003",
                                        "type": "text",
                                        "text": {"body": "Есть вопрос по доставке"},
                                    }
                                ],
                            }
                        }
                    ]
                }
            ]
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["ok"] is True
    assert payload["thread_id"].startswith("wht_")
    assert payload["message_id"].startswith("wam_")

    messages = whatsapp_client.get(
        f"/integrations/whatsapp/threads/{payload['thread_id']}/messages",
        headers=admin_headers(),
    )
    assert messages.status_code == 200
    message = messages.json()[0]
    assert message["text"] == "Есть вопрос по доставке"
    assert message["author_type"] == "customer"
    assert message["whatsapp_message_id_external"] == "wam_meta_1"


def test_provider_webhook_verification_returns_challenge(monkeypatch):
    monkeypatch.setenv("WHATSAPP_META_VERIFY_TOKEN", "wa-verify-token")
    whatsapp_client = TestClient(whatsapp_app)

    response = whatsapp_client.get(
        "/integrations/whatsapp/provider/webhook",
        params={
            "hub.mode": "subscribe",
            "hub.verify_token": "wa-verify-token",
            "hub.challenge": "challenge-123",
        },
    )

    assert response.status_code == 200
    assert response.text == "challenge-123"


def test_provider_webhook_accepts_meta_signature(monkeypatch):
    monkeypatch.setenv("WHATSAPP_APP_SECRET", "meta-app-secret")
    whatsapp_client = TestClient(whatsapp_app)
    payload = {
        "entry": [
            {
                "changes": [
                    {
                        "value": {
                            "contacts": [{"wa_id": "77015550013", "profile": {"name": "Signed Lead"}}],
                            "messages": [
                                {
                                    "id": "wam_meta_signed_1",
                                    "from": "77015550013",
                                    "type": "text",
                                    "text": {"body": "Signed inbound"},
                                }
                            ],
                        }
                    }
                ]
            }
        ]
    }
    body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")

    response = whatsapp_client.post(
        "/integrations/whatsapp/provider/webhook",
        headers={
            "Content-Type": "application/json",
            "X-Hub-Signature-256": _meta_signature("meta-app-secret", body),
        },
        content=body,
    )

    assert response.status_code == 200
    assert response.json()["ok"] is True
    assert response.json()["message_id"].startswith("wam_")


def test_provider_webhook_status_callback_updates_delivery(monkeypatch):
    patch_interaction_request(monkeypatch)
    monkeypatch.setattr(whatsapp_module, "_start_whatsapp_reply_delivery", lambda message_id: None)
    whatsapp_client = TestClient(whatsapp_app)

    created = whatsapp_client.post(
        "/integrations/whatsapp/webhook",
        json={
            "chat_id": "wa_chat_status",
            "text": "Need status update",
            "external_message_id": "wa_status_in_1",
            "whatsapp_user_id": "wa_user_status",
            "phone_number": "+77015550014",
            "display_name": "Status Lead",
        },
    )
    assert created.status_code == 200
    thread_id = created.json()["thread_id"]

    claimed = whatsapp_client.post(
        f"/integrations/whatsapp/threads/{thread_id}/claim",
        headers=operator_headers("operator_status"),
    )
    assert claimed.status_code == 200

    reply = whatsapp_client.post(
        f"/integrations/whatsapp/threads/{thread_id}/messages",
        headers=operator_headers("operator_status"),
        json={"text": "Outbound for status"},
    )
    assert reply.status_code == 200
    reply_message_id = reply.json()["message_id"]

    session = get_session()
    try:
        row = session.execute(
            select(WhatsAppMessageRow).where(WhatsAppMessageRow.message_id == reply_message_id)
        ).scalar_one()
        row.whatsapp_message_id_external = "wam_status_meta_1"
        row.delivery_status = "sent"
        session.commit()
    finally:
        session.close()

    response = whatsapp_client.post(
        "/integrations/whatsapp/provider/webhook",
        json={
            "entry": [
                {
                    "changes": [
                        {
                            "value": {
                                "statuses": [
                                    {
                                        "id": "wam_status_meta_1",
                                        "status": "delivered",
                                        "recipient_id": "77015550014",
                                    }
                                ]
                            }
                        }
                    ]
                }
            ]
        },
    )

    assert response.status_code == 200
    assert response.json()["status_event"] is True
    assert response.json()["updated"] is True
    assert response.json()["delivery_status"] == "delivered"

    session = get_session()
    try:
        row = session.execute(
            select(WhatsAppMessageRow).where(WhatsAppMessageRow.message_id == reply_message_id)
        ).scalar_one()
        assert row.delivery_status == "delivered"
    finally:
        session.close()


def test_send_whatsapp_message_uses_meta_graph_api(monkeypatch):
    captured: dict[str, object] = {}

    class DummyMetaResponse:
        status_code = 200
        text = '{"messages":[{"id":"wam_meta_sent_42"}]}'

        @staticmethod
        def json() -> dict:
            return {"messages": [{"id": "wam_meta_sent_42"}]}

    class DummyMetaClient:
        def __init__(self, timeout: float):
            captured["timeout"] = timeout

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc, tb):
            return None

        def post(self, url, json=None, headers=None):  # noqa: ANN001, ANN201
            captured["url"] = url
            captured["json"] = json
            captured["headers"] = headers
            return DummyMetaResponse()

    monkeypatch.setenv("WHATSAPP_META_ACCESS_TOKEN", "meta-token")
    monkeypatch.setenv("WHATSAPP_META_PHONE_NUMBER_ID", "973115089226218")
    monkeypatch.setattr(whatsapp_module.httpx, "Client", DummyMetaClient)

    payload = whatsapp_module._send_whatsapp_message("77015550015", "Meta outbound hello")

    assert payload["ok"] is True
    assert payload["result"]["message_id"] == "wam_meta_sent_42"
    assert captured["url"] == "https://graph.facebook.com/v22.0/973115089226218/messages"
    assert captured["headers"]["Authorization"] == "Bearer meta-token"
    assert captured["json"] == {
        "messaging_product": "whatsapp",
        "to": "77015550015",
        "type": "text",
        "text": {"body": "Meta outbound hello"},
    }


def test_reply_flow_claims_and_delivers_pending_message(monkeypatch):
    patch_interaction_request(monkeypatch)
    started: list[str] = []
    monkeypatch.setattr(whatsapp_module, "_start_whatsapp_reply_delivery", lambda message_id: started.append(message_id))

    whatsapp_client = TestClient(whatsapp_app)
    created = whatsapp_client.post(
        "/integrations/whatsapp/webhook",
        json={
            "chat_id": "wa_chat_reply",
            "text": "Нужен ответ",
            "external_message_id": "wa_reply_in_1",
            "whatsapp_user_id": "wa_user_reply",
            "phone_number": "+77015550004",
            "display_name": "Reply Lead",
        },
    )
    assert created.status_code == 200
    thread_id = created.json()["thread_id"]

    claimed = whatsapp_client.post(
        f"/integrations/whatsapp/threads/{thread_id}/claim",
        headers=operator_headers("operator_wa"),
    )
    assert claimed.status_code == 200
    assert claimed.json()["unread_count"] == 1

    reply = whatsapp_client.post(
        f"/integrations/whatsapp/threads/{thread_id}/messages",
        headers=operator_headers("operator_wa"),
        json={"text": "Отправляем подтверждение"},
    )
    assert reply.status_code == 200
    payload = reply.json()
    assert payload["delivery_status"] == "pending"
    assert started == [payload["message_id"]]

    monkeypatch.setattr(
        whatsapp_module,
        "_send_whatsapp_message",
        lambda chat_id, text: {"ok": True, "result": {"message_id": "wam_sent_1", "chat_id": chat_id, "text": text}},
    )
    assert whatsapp_module._deliver_pending_whatsapp_reply(payload["message_id"]) is True

    messages = whatsapp_client.get(
        f"/integrations/whatsapp/threads/{thread_id}/messages",
        headers=admin_headers(),
    )
    assert messages.status_code == 200
    delivered = messages.json()[-1]
    assert delivered["delivery_status"] == "sent"
    assert delivered["whatsapp_message_id_external"] == "wam_sent_1"

    session = get_session()
    try:
        row = session.execute(
            select(WhatsAppMessageRow).where(WhatsAppMessageRow.message_id == payload["message_id"])
        ).scalar_one()
        thread = session.execute(
            select(WhatsAppThreadRow).where(WhatsAppThreadRow.thread_id == thread_id)
        ).scalar_one()
        assert row.delivery_status == "sent"
        assert row.whatsapp_message_id_external == "wam_sent_1"
        assert thread.ai_state == "human_owned"
    finally:
        session.close()

    thread_list = whatsapp_client.get("/integrations/whatsapp/threads", headers=admin_headers())
    assert thread_list.status_code == 200
    listed_thread = next(item for item in thread_list.json() if item["thread_id"] == thread_id)
    assert listed_thread["unread_count"] == 0
