"""
SmartStop Sentinel Connect – Module 5
Export Service (State Reporter & PSO Uploader)
---------------------------------------------
Consumes decision packets from Kafka `events.decided` and carries out the
required dispatch actions:
  • Builds state-specific XML/CSV and posts via SFTP or REST
  • Formats PSO Common Format XML and uploads via HTTPS
  • Emits an ACK record onto `events.exported`
Retries with exponential back-off; logs outcome for the immutable ledger.
"""
from __future__ import annotations
import json
import os
import time
from datetime import datetime
from pathlib import Path
from typing import Dict

import requests
import xml.etree.ElementTree as ET
from confluent_kafka import Consumer, Producer, KafkaError

# Kafka topics
BOOTSTRAP = os.getenv("KAFKA_BOOTSTRAP", "localhost:9092")
DECIDED_TOPIC = os.getenv("DECIDED_TOPIC", "events.decided")
EXPORTED_TOPIC = os.getenv("EXPORTED_TOPIC", "events.exported")
GROUP_ID = os.getenv("GROUP_ID", "exporter-service")

consumer_conf = {
    "bootstrap.servers": BOOTSTRAP,
    "group.id": GROUP_ID,
    "auto.offset.reset": "earliest",
}
producer_conf = {"bootstrap.servers": BOOTSTRAP}
consumer = Consumer(consumer_conf)
producer = Producer(producer_conf)

# ------------------------------------------------------------------
# Dispatch Helpers
# ------------------------------------------------------------------

STATE_ENDPOINTS = {
    "CA": "https://eqrm.cdph.ca.gov/api/report",
    "FL": "sftp://fl-ahca.gov/inbox",
    "NY": "https://herds.health.ny.gov/api/upload",
}

PSO_ENDPOINT = os.getenv("PSO_ENDPOINT", "https://pso.example.org/ingest")
PSO_AUTH = os.getenv("PSO_TOKEN", "demo_token")

RETRY_LIMIT = 5
BACKOFF_BASE = 2  # seconds


def build_state_xml(ev: Dict[str, str]) -> str:
    root = ET.Element("SentinelEvent")
    ET.SubElement(root, "EventID").text = ev["event_id"]
    ET.SubElement(root, "OrgID").text = ev["org_id"]
    ET.SubElement(root, "Timestamp").text = ev["timestamp"]
    return ET.tostring(root, encoding="utf-8").decode()


def send_state_report(state: str, payload: str) -> bool:
    for attempt in range(RETRY_LIMIT):
        try:
            ep = STATE_ENDPOINTS[state]
            if ep.startswith("https"):
                resp = requests.post(ep, data=payload, timeout=10)
                if resp.ok:
                    return True
            else:  # rudimentary SFTP via sshpass (placeholder)
                tmp = Path("/tmp/report.xml")
                tmp.write_text(payload)
                os.system(f"sshpass -p $SFTP_PASS scp {tmp} {ep}")
                return True
        except Exception as exc:
            wait = BACKOFF_BASE ** attempt
            time.sleep(wait)
    return False


def send_pso_report(ev: Dict[str, str]) -> bool:
    headers = {"Authorization": f"Bearer {PSO_AUTH}"}
    try:
        resp = requests.post(PSO_ENDPOINT, json=ev, headers=headers, timeout=10)
        return resp.ok
    except Exception:
        return False

# ------------------------------------------------------------------
# Main loop
# ------------------------------------------------------------------

def main():
    consumer.subscribe([DECIDED_TOPIC])
    while True:
        msg = consumer.poll(1.0)
        if msg is None:
            continue
        if msg.error() and msg.error().code() != KafkaError._PARTITION_EOF:
            print("Kafka error", msg.error())
            continue
        try:
            dec = json.loads(msg.value())
            org_state = dec["org_id"][:2]  # crude: first 2 chars as state code

            sent = []
            if dec["state_report"]:
                xml_body = build_state_xml(dec)
                ok = send_state_report(org_state, xml_body)
                sent.append("state" if ok else "state_fail")
            if dec["pso_flag"]:
                ok = send_pso_report(dec)
                sent.append("pso" if ok else "pso_fail")

            ack = {
                "event_id": dec["event_id"],
                "org_id": dec["org_id"],
                "outputs": sent,
                "timestamp": datetime.utcnow().isoformat(),
            }
            producer.produce(EXPORTED_TOPIC, json.dumps(ack).encode())
        except Exception as exc:
            print("export error", exc)
        producer.poll(0)

if __name__ == "__main__":
    main()
