from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone

from fastapi.testclient import TestClient

from app.api.v1 import device as device_api
from app.core import heartbeat_monitor_worker
from app.db.session import SessionLocal
from app.integrations import vendor_push
from app.models.device import DeviceLiveness
from app.repositories import user as user_repo


class _Resp:
    status_code = 200
    text = "{}"

    def __init__(self, data: dict) -> None:
        self._data = data

    def json(self) -> dict:
        return self._data


def test_xiaomi_accessibility_payload(monkeypatch) -> None:
    captured: dict = {}

    def _fake_request(method, url, **kwargs):  # noqa: ANN001
        captured.update(method=method, url=url, **kwargs)
        return _Resp({"code": 0, "result": "ok", "data": {"id": "xm-msg"}})

    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_APP_SECRET", "xiaomi-secret")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_CHANNEL_ID", "")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_ID", "")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_TITLE", "")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_DESCRIPTION", "")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_PARAM_JSON", "")
    monkeypatch.setattr(vendor_push.httpx, "request", _fake_request)

    data = vendor_push.send_accessibility_disabled("xiaomi", "xm-regid")

    assert data["data"]["id"] == "xm-msg"
    assert captured["method"] == "POST"
    assert captured["url"] == vendor_push.settings.XIAOMI_PUSH_SEND_ENDPOINT
    assert captured["headers"]["Authorization"] == "key=xiaomi-secret"
    body = captured["data"]
    assert body["registration_id"] == "xm-regid"
    assert body["restricted_package_name"] == "com.jishisongfu.shaguabijia"
    assert json.loads(body["payload"]) == {"type": "accessibility_disabled"}
    assert "extra.channel_id" not in body


def test_xiaomi_payload_with_channel_and_template(monkeypatch) -> None:
    captured: dict = {}

    def _fake_request(method, url, **kwargs):  # noqa: ANN001
        captured.update(method=method, url=url, **kwargs)
        return _Resp({"code": 0, "result": "ok", "data": {"id": "xm-msg"}})

    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_APP_SECRET", "xiaomi-secret")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_CHANNEL_ID", "130")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_ID", "1001")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_TITLE", "{$app_name$}提醒")
    monkeypatch.setattr(vendor_push.settings, "XIAOMI_PUSH_TEMPLATE_DESCRIPTION", "{$content$}")
    monkeypatch.setattr(
        vendor_push.settings,
        "XIAOMI_PUSH_TEMPLATE_PARAM_JSON",
        '{"app_name":"傻瓜比价","content":"{alert}"}',
    )
    monkeypatch.setattr(vendor_push.httpx, "request", _fake_request)

    vendor_push.send_accessibility_disabled(
        "xiaomi",
        "xm-regid",
        title="测试标题",
        alert="测试内容",
    )

    body = captured["data"]
    assert body["title"] == "{$app_name$}提醒"
    assert body["description"] == "{$content$}"
    assert body["extra.channel_id"] == "130"
    assert body["extra.template_id"] == "1001"
    assert body["extra.template_param"] == '{"app_name":"傻瓜比价","content":"测试内容"}'


def test_vivo_auth_and_send_payload(monkeypatch) -> None:
    vendor_push._token_cache.clear()
    calls: list[dict] = []

    def _fake_request(method, url, **kwargs):  # noqa: ANN001
        calls.append({"method": method, "url": url, **kwargs})
        if url == vendor_push.settings.VIVO_PUSH_AUTH_ENDPOINT:
            return _Resp({"result": 0, "authToken": "vivo-auth"})
        return _Resp({"result": 0, "taskId": "vivo-task"})

    monkeypatch.setattr(vendor_push.settings, "VIVO_PUSH_APP_ID", "106072775")
    monkeypatch.setattr(vendor_push.settings, "VIVO_PUSH_APP_KEY", "vivo-key")
    monkeypatch.setattr(vendor_push.settings, "VIVO_PUSH_APP_SECRET", "vivo-secret")
    monkeypatch.setattr(vendor_push.httpx, "request", _fake_request)

    data = vendor_push.send_accessibility_disabled("vivo", "vivo-regid")

    assert data["taskId"] == "vivo-task"
    assert calls[0]["url"] == vendor_push.settings.VIVO_PUSH_AUTH_ENDPOINT
    assert calls[0]["json"]["appId"] == "106072775"
    assert calls[0]["json"]["sign"]
    assert calls[1]["url"] == vendor_push.settings.VIVO_PUSH_SEND_ENDPOINT
    assert calls[1]["headers"]["authToken"] == "vivo-auth"
    body = calls[1]["json"]
    assert body["regId"] == "vivo-regid"
    assert body["pushMode"] == vendor_push.settings.VIVO_PUSH_MODE
    assert body["clientCustomMap"] == {"type": "accessibility_disabled"}


def test_oppo_auth_and_send_payload(monkeypatch) -> None:
    vendor_push._token_cache.clear()
    calls: list[dict] = []

    def _fake_request(method, url, **kwargs):  # noqa: ANN001
        calls.append({"method": method, "url": url, **kwargs})
        if url == vendor_push.settings.OPPO_PUSH_AUTH_ENDPOINT:
            return _Resp({"code": 0, "data": {"auth_token": "oppo-auth"}})
        return _Resp({"code": 0, "data": {"message_id": "oppo-msg"}})

    monkeypatch.setattr(vendor_push.settings, "OPPO_PUSH_APP_KEY", "oppo-key")
    monkeypatch.setattr(vendor_push.settings, "OPPO_PUSH_MASTER_SECRET", "oppo-master")
    monkeypatch.setattr(vendor_push.httpx, "request", _fake_request)

    data = vendor_push.send_accessibility_disabled("oppo", "oppo-regid")

    assert data["data"]["message_id"] == "oppo-msg"
    assert calls[0]["data"]["app_key"] == "oppo-key"
    assert calls[0]["data"]["sign"]
    message = json.loads(calls[1]["data"]["message"])
    assert calls[1]["data"]["auth_token"] == "oppo-auth"
    assert message["target_type"] == 2
    assert message["target_value"] == "oppo-regid"
    assert json.loads(message["notification"]["action_parameters"]) == {
        "type": "accessibility_disabled"
    }


def test_honor_auth_and_send_payload(monkeypatch) -> None:
    vendor_push._token_cache.clear()
    calls: list[dict] = []

    def _fake_request(method, url, **kwargs):  # noqa: ANN001
        calls.append({"method": method, "url": url, **kwargs})
        if url == vendor_push.settings.HONOR_PUSH_TOKEN_ENDPOINT:
            return _Resp({"access_token": "honor-access", "expires_in": 3600})
        return _Resp({"code": 200, "message": "successful!", "data": {"sendResult": True}})

    monkeypatch.setattr(vendor_push.settings, "HONOR_PUSH_APP_ID", "104559789")
    monkeypatch.setattr(vendor_push.settings, "HONOR_PUSH_CLIENT_ID", "honor-client")
    monkeypatch.setattr(vendor_push.settings, "HONOR_PUSH_CLIENT_SECRET", "honor-secret")
    monkeypatch.setattr(vendor_push.httpx, "request", _fake_request)

    data = vendor_push.send_accessibility_disabled("honor", "honor-token")

    assert data["code"] == 200
    assert calls[0]["data"]["client_id"] == "honor-client"
    assert calls[1]["headers"]["Authorization"] == "Bearer honor-access"
    assert calls[1]["headers"]["timestamp"]
    assert calls[1]["url"].endswith("/api/v1/104559789/sendMessage")
    body = calls[1]["json"]
    assert body["token"] == ["honor-token"]
    assert body["android"]["targetUserType"] == 1
    assert body["android"]["notification"]["clickAction"] == {"type": 3}
    assert json.loads(body["data"]) == {"type": "accessibility_disabled"}


def _seed_overdue_device(
    *,
    phone: str,
    device_id: str,
    push_vendor: str | None,
    push_token: str | None,
) -> int:
    with SessionLocal() as db:
        user = user_repo.upsert_user_for_login(db, phone=phone, register_channel="sms")
        device = DeviceLiveness(
            user_id=user.id,
            device_id=device_id,
            push_vendor=push_vendor,
            push_token=push_token,
            platform="android",
            ever_protected=True,
            last_heartbeat_at=datetime.now(timezone.utc) - timedelta(minutes=30),  # noqa: UP017
            last_report_protection_on=True,
            liveness_state="alive",
            kill_alert_pending=False,
        )
        db.add(device)
        db.commit()
        db.refresh(device)
        return device.id


def _login(client: TestClient, phone: str) -> str:
    client.post("/api/v1/auth/sms/send", json={"phone": phone})
    r = client.post("/api/v1/auth/sms/login", json={"phone": phone, "code": "123456"})
    assert r.status_code == 200, r.text
    return r.json()["access_token"]


def _auth(token: str) -> dict[str, str]:
    return {"Authorization": f"Bearer {token}"}


def test_heartbeat_monitor_pushes_overdue_device(monkeypatch) -> None:
    device_pk = _seed_overdue_device(
        phone="13900009001",
        device_id="dev-push-honor",
        push_vendor="honor",
        push_token="honor-token-1",
    )
    calls: list[tuple[str, str]] = []

    def _fake_send(push_vendor: str, push_token: str) -> dict:
        calls.append((push_vendor, push_token))
        return {"msg_id": "m1"}

    monkeypatch.setattr(
        heartbeat_monitor_worker.vendor_push,
        "send_accessibility_disabled",
        _fake_send,
    )

    result = heartbeat_monitor_worker._scan_once(timeout_minutes=10)

    assert result["pushed"] >= 1
    assert ("honor", "honor-token-1") in calls
    with SessionLocal() as db:
        device = db.get(DeviceLiveness, device_pk)
        assert device is not None
        assert device.liveness_state == "notified"
        assert device.kill_alert_pending is True


def test_heartbeat_monitor_skips_push_without_vendor_token(monkeypatch) -> None:
    device_pk = _seed_overdue_device(
        phone="13900009002",
        device_id="dev-push-no-token",
        push_vendor=None,
        push_token=None,
    )

    def _fake_send(push_vendor: str, push_token: str) -> dict:
        raise AssertionError(f"should not push without token: {push_vendor}/{push_token}")

    monkeypatch.setattr(
        heartbeat_monitor_worker.vendor_push,
        "send_accessibility_disabled",
        _fake_send,
    )

    result = heartbeat_monitor_worker._scan_once(timeout_minutes=10)

    assert result["checked"] >= 1
    with SessionLocal() as db:
        device = db.get(DeviceLiveness, device_pk)
        assert device is not None
        assert device.liveness_state == "notified"
        assert device.kill_alert_pending is True


def test_push_test_endpoint_schedules_vendor_push(client: TestClient, monkeypatch) -> None:
    token = _login(client, "13900009003")
    calls: list[tuple[str, str, str, str]] = []

    def _fake_send(push_vendor: str, push_token: str, *, title: str, alert: str) -> dict:
        calls.append((push_vendor, push_token, title, alert))
        return {"msg_id": "m-test"}

    monkeypatch.setattr(device_api.vendor_push, "send_accessibility_disabled", _fake_send)

    r = client.post(
        "/api/v1/device/push-test",
        json={
            "device_id": "dev-push-test",
            "push_vendor": "honor",
            "push_token": "honor-test-token",
            "delay_seconds": 0,
        },
        headers=_auth(token),
    )

    assert r.status_code == 200, r.text
    assert r.json() == {
        "ok": True,
        "delay_seconds": 0,
        "has_push_token": True,
    }
    assert calls == [
        (
            "honor",
            "honor-test-token",
            "测试推送",
            "这是一条厂商通道测试推送。收到它说明 App 被划掉后仍可通过系统通知栏触达。",
        )
    ]


def test_push_test_endpoint_requires_vendor_token(client: TestClient) -> None:
    token = _login(client, "13900009004")

    r = client.post(
        "/api/v1/device/push-test",
        json={"device_id": "dev-push-test-no-token", "delay_seconds": 0},
        headers=_auth(token),
    )

    assert r.status_code == 409
    assert r.json()["detail"] == "push vendor token not ready"
