#!/usr/bin/env python3
"""
Manual canary validation helper for TV power-intent Phase 1 server publishing.

This script demonstrates expected power-intent payloads and validates the
computation and publishing logic without requiring a full broker connection.

Usage:
  python test_power_intent_canary.py
"""

import json
import sys
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

sys.path.insert(0, '/workspace')

from scheduler.db_utils import (
    compute_group_power_intent_basis,
    build_group_power_intent_body,
    compute_group_power_intent_fingerprint,
)


def utc_now():
    """Get UTC now."""
    return datetime.now(timezone.utc)


def test_scenario_1_no_active_events():
    """Scenario 1: No active events => OFF intent."""
    print("\n" + "="*70)
    print("SCENARIO 1: No active events => desired_state=OFF")
    print("="*70)
    
    now = utc_now()
    events = []  # empty group
    group_id = 1
    
    intent_basis = compute_group_power_intent_basis(
        events=events,
        group_id=group_id,
        now_utc=now
    )
    
    desired_state = intent_basis["desired_state"]
    reason = intent_basis["reason"]
    
    assert desired_state == "off", f"Expected 'off', got '{desired_state}'"
    assert reason == "no_active_event", f"Expected reason 'no_active_event', got '{reason}'"
    
    payload_body = build_group_power_intent_body(intent_basis, poll_interval_sec=15)
    
    print(f"✓ Desired state: {desired_state}")
    print(f"✓ Reason: {reason}")
    print(f"✓ Event window: {intent_basis.get('event_window_start')} to {intent_basis.get('event_window_end')}")
    print(f"✓ Payload body (pretty-print):")
    print(json.dumps(payload_body, indent=2))
    
    # Validate payload structure
    assert "desired_state" in payload_body
    assert payload_body["desired_state"] == "off"
    assert "group_id" in payload_body
    print("✓ Payload structure validated")


def test_scenario_2_single_active_event():
    """Scenario 2: One active event now => ON intent."""
    print("\n" + "="*70)
    print("SCENARIO 2: One active event now => desired_state=ON")
    print("="*70)
    
    now = utc_now()
    start = now - timedelta(seconds=60)
    end = now + timedelta(seconds=300)
    group_id = 1
    
    events = [
        {
            "id": 101,
            "group_id": group_id,
            "start": start.isoformat(),
            "end": end.isoformat(),
            "subject": "Morning Announcement",
            "event_type": "message",
        }
    ]
    
    intent_basis = compute_group_power_intent_basis(
        events=events,
        group_id=group_id,
        now_utc=now
    )
    
    desired_state = intent_basis["desired_state"]
    reason = intent_basis["reason"]
    
    assert desired_state == "on", f"Expected 'on', got '{desired_state}'"
    assert reason == "active_event", f"Expected reason 'active_event', got '{reason}'"
    
    payload_body = build_group_power_intent_body(intent_basis, poll_interval_sec=15)
    
    print(f"✓ Desired state: {desired_state}")
    print(f"✓ Reason: {reason}")
    print(f"✓ event_window_start: {intent_basis.get('event_window_start')}")
    print(f"✓ event_window_end: {intent_basis.get('event_window_end')}")
    print(f"✓ Payload body (pretty-print):")
    print(json.dumps(payload_body, indent=2))
    
    assert payload_body["desired_state"] == "on"
    print("✓ Payload structure validated")


def test_scenario_3_adjacent_events_no_off_blip():
    """Scenario 3: Adjacent events (no gap) => no OFF blip between them."""
    print("\n" + "="*70)
    print("SCENARIO 3: Adjacent events => no OFF between them")
    print("="*70)
    
    # Event 1: ends at T+300
    # Event 2: starts at T+300 (adjacent, no gap)
    base = utc_now()
    group_id = 2
    
    events_at_boundary = [
        {
            "id": 201,
            "group_id": group_id,
            "start": (base + timedelta(seconds=0)).isoformat(),
            "end": (base + timedelta(seconds=300)).isoformat(),
            "subject": "Event 1",
            "event_type": "presentation",
        },
        {
            "id": 202,
            "group_id": group_id,
            "start": (base + timedelta(seconds=300)).isoformat(),
            "end": (base + timedelta(seconds=600)).isoformat(),
            "subject": "Event 2",
            "event_type": "presentation",
        },
    ]
    
    # Sample times: before, at boundary, and after
    scenarios = [
        ("Before boundary (Event 1 active)", base + timedelta(seconds=150)),
        ("At boundary (no gap)", base + timedelta(seconds=300)),
        ("After boundary (Event 2 active)", base + timedelta(seconds=450)),
    ]
    
    for label, sample_time in scenarios:
        intent_basis = compute_group_power_intent_basis(
            events=events_at_boundary,
            group_id=group_id,
            now_utc=sample_time
        )
        desired_state = intent_basis["desired_state"]
        reason = intent_basis["reason"]
        
        print(f"\n  {label}:")
        print(f"    Desired state: {desired_state}")
        print(f"    Reason: {reason}")
        
        assert desired_state == "on", f"Expected 'on' at {label}, got '{desired_state}'"
    
    print("\n✓ All boundary times show 'on' => no OFF blip between adjacent events")


def test_scenario_4_gap_between_events():
    """Scenario 4: Gap between events => OFF when not covered."""
    print("\n" + "="*70)
    print("SCENARIO 4: Gap between events => OFF during gap")
    print("="*70)
    
    base = utc_now()
    group_id = 3
    
    events_with_gap = [
        {
            "id": 301,
            "group_id": group_id,
            "start": (base + timedelta(seconds=0)).isoformat(),
            "end": (base + timedelta(seconds=300)).isoformat(),
            "subject": "Event 1",
            "event_type": "presentation",
        },
        {
            "id": 302,
            "group_id": group_id,
            "start": (base + timedelta(seconds=600)).isoformat(),
            "end": (base + timedelta(seconds=900)).isoformat(),
            "subject": "Event 2",
            "event_type": "presentation",
        },
    ]
    
    # Sample during gap: T+450 is between end(300) and start(600)
    gap_time = base + timedelta(seconds=450)
    
    intent_basis = compute_group_power_intent_basis(
        events=events_with_gap,
        group_id=group_id,
        now_utc=gap_time
    )
    
    desired_state = intent_basis["desired_state"]
    reason = intent_basis["reason"]
    
    print(f"Sample time: {gap_time.isoformat()}")
    print(f"Desired state: {desired_state}")
    print(f"Reason: {reason}")
    
    assert desired_state == "off", f"Expected 'off' during gap, got '{desired_state}'"
    print("✓ Correctly recognizes gap => OFF")


def test_scenario_5_semantic_fingerprint_stable():
    """Scenario 5: Semantic fingerprint is stable for unchanged state."""
    print("\n" + "="*70)
    print("SCENARIO 5: Semantic fingerprint stability (transition detection)")
    print("="*70)
    
    payload_body_1 = {
        "schema_version": "1.0",
        "group_id": 5,
        "desired_state": "on",
        "reason": "active_event",
        "poll_interval_sec": 15,
        "event_window_start": "2026-03-31T20:15:00Z",
        "event_window_end": "2026-03-31T20:20:00Z",
        "active_event_ids": [501],
    }
    
    payload_body_2 = {
        "schema_version": "1.0",
        "group_id": 5,
        "desired_state": "on",
        "reason": "active_event",
        "poll_interval_sec": 15,
        "event_window_start": "2026-03-31T20:15:00Z",
        "event_window_end": "2026-03-31T20:20:00Z",
        "active_event_ids": [501],
    }
    
    payload_body_3_different = {
        "schema_version": "1.0",
        "group_id": 5,
        "desired_state": "off",  # Changed
        "reason": "no_active_event",
        "poll_interval_sec": 15,
        "event_window_start": None,
        "event_window_end": None,
        "active_event_ids": [],
    }
    
    fp1 = compute_group_power_intent_fingerprint(payload_body_1)
    fp2 = compute_group_power_intent_fingerprint(payload_body_2)
    fp3 = compute_group_power_intent_fingerprint(payload_body_3_different)
    
    print(f"Payload 1 (on, event X): {fp1}")
    print(f"Payload 2 (on, same event X): {fp2}")
    print(f"Payload 3 (off, no event): {fp3}")
    
    assert fp1 == fp2, "Identical payloads should have same fingerprint"
    assert fp1 != fp3, "Different desired_state should have different fingerprint"
    
    print("✓ Fingerprint is stable for same state (no spurious transitions)")
    print("✓ Fingerprint changes on semantic transition")


def test_scenario_6_timestamp_format_validation():
    """Scenario 6: Payload body contains window start/end in UTC Z format."""
    print("\n" + "="*70)
    print("SCENARIO 6: Event window timestamp format validation")
    print("="*70)
    
    now = utc_now()
    group_id = 6
    events = [
        {
            "id": 601,
            "group_id": group_id,
            "start": (now - timedelta(seconds=60)).isoformat(),
            "end": (now + timedelta(seconds=300)).isoformat(),
            "subject": "Event",
            "event_type": "message",
        }
    ]
    
    intent_basis = compute_group_power_intent_basis(
        events=events,
        group_id=group_id,
        now_utc=now
    )
    
    window_start = intent_basis.get("event_window_start")
    window_end = intent_basis.get("event_window_end")
    
    print(f"Event window start: {window_start}")
    print(f"Event window end: {window_end}")
    
    if window_start:
        assert window_start.endswith("Z"), f"event_window_start must end with Z: {window_start}"
    if window_end:
        assert window_end.endswith("Z"), f"event_window_end must end with Z: {window_end}"
    
    # Validate they are valid RFC3339 timestamps
    try:
        if window_start:
            dt_start = datetime.fromisoformat(window_start.replace("Z", "+00:00"))
        if window_end:
            dt_end = datetime.fromisoformat(window_end.replace("Z", "+00:00"))
            if window_start:
                assert dt_end > dt_start, "window_end must be after window_start"
        print("✓ Event window timestamps are valid RFC3339 UTC format with Z suffix")
    except Exception as e:
        print(f"✗ Timestamp parsing failed: {e}")
        raise


def main():
    """Run all scenarios."""
    print("\n" + "="*70)
    print("TV POWER INTENT PHASE-1 SERVER CANARY VALIDATION")
    print("="*70)
    print("\nThis script validates server-side power-intent computation logic")
    print("without requiring an MQTT broker connection.\n")
    
    try:
        test_scenario_1_no_active_events()
        test_scenario_2_single_active_event()
        test_scenario_3_adjacent_events_no_off_blip()
        test_scenario_4_gap_between_events()
        test_scenario_5_semantic_fingerprint_stable()
        test_scenario_6_timestamp_format_validation()
        
        print("\n" + "="*70)
        print("ALL CANARY SCENARIOS PASSED ✓")
        print("="*70)
        print("\nNext steps for full validation:")
        print("1. Enable POWER_INTENT_PUBLISH_ENABLED=true in scheduler")
        print("2. Subscribe to infoscreen/groups/+/power/intent in MQTT broker")
        print("3. Run scheduler and observe:")
        print("   - ON payload on event start")
        print("   - Same intent_id across heartbeat republishes")
        print("   - OFF payload on event end")
        print("   - No OFF blip between adjacent events")
        print("4. Restart scheduler and verify immediate ON republish")
        print("5. Disconnect MQTT broker and verify reconnect republish")
        print("\nSee TV_POWER_CANARY_VALIDATION_CHECKLIST.md for full validation matrix.")
        
        return 0
    except AssertionError as e:
        print(f"\n✗ VALIDATION FAILED: {e}")
        return 1
    except Exception as e:
        print(f"\n✗ UNEXPECTED ERROR: {e}")
        import traceback
        traceback.print_exc()
        return 1


if __name__ == "__main__":
    sys.exit(main())
