"""Tests for compass direction fallback on satpass_predict envelopes.

Proves the fix for empty compass on satpass_predict broadcasts:
 (a) satpass_predict-shape envelope (raw azimuth degrees, NO _compass fields)
     produces non-empty compass directions in the wire message.
 (b) n2yo-shape envelope with precomputed _compass strings is unchanged.
 (c) Envelope with neither raw azimuths nor _compass strings produces
     empty compass, no crash.

Uses verbatim field shapes from the live AO-27 predict envelope.
"""
from __future__ import annotations

import copy
import json

import pytest


# ── Live AO-27 satpass_predict envelope (no _compass fields) ─────────

AO27_PREDICT_ENVELOPE = {
    "id": "filer:36122:2026-06-13T06:12:00+00:00",
    "source": "central.echo6.co",
    "type": "central.pass.satpass_predict.v1",
    "time": "2026-06-13T06:12:00+00:00",
    "datacontenttype": "application/json",
    "centralschemaversion": "1.0",
    "centralcategory": "pass.satpass_predict",
    "centralseverity": 1,
    "specversion": "1.0",
    "data": {
        "id": "filer:36122:2026-06-13T06:12:00+00:00",
        "adapter": "satpass_predict",
        "category": "pass.satpass_predict",
        "time": "2026-06-13T06:12:00Z",
        "expires": None,
        "severity": 1,
        "geo": {
            "centroid": [-114.6, 42.57],
            "bbox": None,
            "regions": ["US-ID"],
            "primary_region": "US-ID",
            "geometry": None,
        },
        "data": {
            "observer_name": "Filer",
            "observer_slug": "filer",
            "observer_state": "ID",
            "norad_id": 36122,
            "satellite_name": "EYESAT A (AO-27)",
            "aos_time": "2026-06-13T06:12:00+00:00",
            "peak_time": "2026-06-13T06:18:00+00:00",
            "los_time": "2026-06-13T06:24:00+00:00",
            "max_elevation_deg": 62.3,
            "azimuth_at_aos": 163.2,
            "azimuth_at_peak": 245.0,
            "azimuth_at_los": 348.7,
            "duration_s": 720,
        },
    },
}


# ── n2yo envelope with precomputed _compass strings ──────────────────

N2YO_ENVELOPE = {
    "id": "filer:28654:2026-06-10T04:34:40+00:00",
    "source": "central.echo6.co",
    "type": "central.pass.n2yo_visualpasses.v1",
    "time": "2026-06-10T04:41:35+00:00",
    "datacontenttype": "application/json",
    "centralschemaversion": "1.0",
    "centralcategory": "pass.n2yo_visualpasses",
    "centralseverity": 1,
    "specversion": "1.0",
    "data": {
        "id": "filer:28654:2026-06-10T04:34:40+00:00",
        "adapter": "n2yo_visualpasses",
        "category": "pass.n2yo_visualpasses",
        "time": "2026-06-10T04:41:35Z",
        "expires": None,
        "severity": 1,
        "geo": {
            "centroid": [-114.6, 42.57],
            "bbox": None,
            "regions": ["US-ID"],
            "primary_region": "US-ID",
            "geometry": None,
        },
        "data": {
            "observer_name": "Filer",
            "observer_slug": "filer",
            "observer_state": "ID",
            "norad_id": 28654,
            "satellite_name": "NOAA 18",
            "aos_time": "2026-06-10T04:34:40+00:00",
            "peak_time": "2026-06-10T04:41:35+00:00",
            "los_time": "2026-06-10T04:48:30+00:00",
            "max_elevation_deg": 22.69,
            "magnitude": 6.7,
            "azimuth_at_aos": 125.6,
            "azimuth_at_aos_compass": "SE",
            "azimuth_at_peak": 63.0,
            "azimuth_at_peak_compass": "ENE",
            "azimuth_at_los": 359.5,
            "azimuth_at_los_compass": "N",
            "duration_s": 630,
        },
    },
}


def _enable_satpass(norad_ids=None):
    """Set satpass.enabled=true with permissive filters."""
    from meshai.persistence import get_db
    from meshai.adapter_config import invalidate_cache
    conn = get_db()
    conn.execute(
        "UPDATE adapter_config SET value_json='true' "
        "WHERE adapter='satpass' AND key='enabled'"
    )
    conn.execute(
        "UPDATE adapter_config SET value_json='5' "
        "WHERE adapter='satpass' AND key='min_elevation'"
    )
    conn.execute(
        "UPDATE adapter_config SET value_json='false' "
        "WHERE adapter='satpass' AND key='dry_run'"
    )
    if norad_ids is None:
        norad_ids = [36122, 28654]
    conn.execute(
        "UPDATE adapter_config SET value_json=? "
        "WHERE adapter='satpass' AND key='norad_ids'",
        (json.dumps(norad_ids),)
    )
    invalidate_cache()


def _clear_handler_flags():
    from meshai.central.satpass_handler import handle_satpass
    for attr in ("_disabled_logged", "_no_norad_ids_logged"):
        if hasattr(handle_satpass, attr):
            delattr(handle_satpass, attr)


def _ingest_and_consolidate(env, subject, *, now, data=None):
    """Drive the two-call async satpass contract (ingest → consolidate).

    handle_satpass() ingests the pass and returns None; the consumer then
    runs consolidate_satpass_pending(), which yields the (wire, data) to
    broadcast (or None). Returns the consolidation result so a test can
    assert on the real broadcast wire.
    """
    from meshai.central.satpass_handler import (
        handle_satpass, consolidate_satpass_pending,
        drain_pending_consolidation_ids)
    drain_pending_consolidation_ids()
    assert handle_satpass(
        env, subject, data=data if data is not None else {}, now=now) is None
    for cid in drain_pending_consolidation_ids():
        res = consolidate_satpass_pending(cid)
        if res is not None:
            return res
    return None


# ── (a) satpass_predict envelope: raw azimuths → non-empty compass ───

def test_satpass_predict_compass_from_raw_azimuths():
    """satpass_predict envelope with only raw azimuth degrees produces
    non-empty compass directions like SSE→N in wire output."""
    from meshai.central.satpass_handler import handle_satpass

    _enable_satpass()
    _clear_handler_flags()

    now = 1781330520  # well before the AO-27 pass window
    result = _ingest_and_consolidate(
        AO27_PREDICT_ENVELOPE,
        "central.sat.pass.us.id.filer",
        now=now,
    )
    assert result is not None, "handler returned None for satpass_predict envelope"
    wire, _ = result

    lines = wire.split("\n")
    assert len(lines) == 2, f"Expected 2 lines, got {len(lines)}: {wire!r}"

    # Line 1 format: name bucket, aos→peak→los. Raw azimuths convert
    # to a non-empty 3-point compass sweep.
    compass = lines[0].split(", ")[-1]
    parts = compass.split("\u2192")
    assert len(parts) == 3, f"Expected aos->peak->los sweep, got {parts!r}"
    # 163.2 -> S, 245.0 -> SW (peak), 348.7 -> N  (8-point compass)
    assert parts[0] == "S", f"Expected S (from 163.2): {parts!r}"
    assert parts[1] == "SW", f"Expected SW (from peak 245.0): {parts!r}"
    assert parts[2] == "N", f"Expected N (from 348.7): {parts!r}"


# ── (b) n2yo envelope: precomputed _compass strings used as-is ───────

def test_n2yo_precomputed_compass_unchanged():
    """n2yo envelope with _compass string fields uses those strings,
    not raw azimuth conversion."""
    from meshai.central.satpass_handler import handle_satpass

    _enable_satpass()
    _clear_handler_flags()

    now = 1781065800  # before NOAA-18 pass window
    result = _ingest_and_consolidate(
        N2YO_ENVELOPE,
        "central.sat.pass.us.id.filer",
        now=now,
    )
    assert result is not None, "handler returned None for n2yo envelope"
    wire, _ = result

    lines = wire.split("\n")
    # Must use the precomputed strings verbatim: SE→ENE→N
    compass = lines[0].split(", ")[-1]
    parts = compass.split("\u2192")
    assert len(parts) == 3, f"Expected aos->peak->los sweep, got {parts!r}"
    assert parts[0] == "SE", f"Expected precomputed aos SE: {parts!r}"
    assert parts[1] == "ENE", f"Expected precomputed peak ENE: {parts!r}"
    assert parts[2] == "N", f"Expected precomputed los N: {parts!r}"


# ── (c) envelope with neither → empty compass, no crash ──────────────

def test_no_compass_no_azimuth_no_crash():
    """Envelope with no _compass fields AND no raw azimuth fields
    produces empty compass directions without crashing."""
    from meshai.central.satpass_handler import handle_satpass

    _enable_satpass()
    _clear_handler_flags()

    env = copy.deepcopy(AO27_PREDICT_ENVELOPE)
    d = env["data"]["data"]
    # Remove all azimuth fields
    for key in ("azimuth_at_aos", "azimuth_at_los", "azimuth_at_peak",
                "azimuth_at_aos_compass", "azimuth_at_los_compass",
                "azimuth_at_peak_compass"):
        d.pop(key, None)

    now = 1781330520
    result = _ingest_and_consolidate(
        env,
        "central.sat.pass.us.id.filer",
        now=now,
    )
    assert result is not None, "handler crashed or returned None — should produce wire with empty compass"
    wire, _ = result

    lines = wire.split("\n")
    assert len(lines) == 2
    # Arrow still present even with empty (peak empty -> no peak segment):
    assert "\u2192" in lines[0], f"Expected arrow in line 1 even with empty compass: {lines[0]!r}"
    # No crash = test passes
