#!/usr/bin/env python3
from __future__ import annotations

import os
import time
import json
import random
from dataclasses import dataclass
from datetime import datetime, timezone
from uuid import uuid4
from typing import Dict, List, Optional, Tuple

import requests
from token_service import get_or_create_epic_token


FHIR_BASE = os.getenv("FHIR_BASE_URL", "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4")
EPIC_APP_CONFIG_ID = int(os.getenv("EPIC_APP_CONFIG_ID", "3"))

PATIENT_REF = os.getenv("PATIENT_REF", "Patient/e63wRTbPfr1p8UW81d8Seiw3")
ENCOUNTER_REF = os.getenv("ENCOUNTER_REF", "Encounter/ewYPWmestytGzRSBFlutk7w3")

INTERVAL_SECONDS = float(os.getenv("INTERVAL_SECONDS", "1"))
TIMEOUT_SECONDS = int(os.getenv("TIMEOUT_SECONDS", "15"))


def normalize_resource_url(fhir_base: str, resource: str) -> str:
    base = fhir_base.strip().rstrip("/")
    lowered = base.lower()
    r = resource.lower()
    if lowered.endswith(f"/{r}"):
        return base
    if lowered.endswith("/fhir/r4"):
        return base + f"/{resource}"
    return base + f"/{resource}"


def now_fhir_dt() -> str:
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


def get_headers() -> Dict[str, str]:
    token = get_or_create_epic_token(EPIC_APP_CONFIG_ID)
    return {
        "Authorization": f"Bearer {token}",
        "Accept": "application/fhir+json",
        "Content-Type": "application/fhir+json",
    }


def build_observation(
    patient_ref: str,
    encounter_ref: str,
    loinc_code: str,
    display: str,
    value: float,
    unit: str,
    ucum_code: str,
) -> Dict:
    return {
        "resourceType": "Observation",
        "id": uuid4().hex,
        "status": "final",
        "category": [
            {
                "coding": [
                    {
                        "system": "http://terminology.hl7.org/CodeSystem/observation-category",
                        "code": "vital-signs",
                        "display": "Vital Signs",
                    }
                ]
            }
        ],
        "code": {
            "coding": [
                {"system": "http://loinc.org", "code": loinc_code, "display": display}
            ],
            "text": display,
        },
        "subject": {"reference": patient_ref},
        "encounter": {"reference": encounter_ref},
        "effectiveDateTime": now_fhir_dt(),
        "valueQuantity": {
            "value": value,
            "unit": unit,
            "system": "http://unitsofmeasure.org",
            "code": ucum_code,
        },
    }


def is_flowsheet_row_error(resp_json: Dict) -> bool:
    # Detect Epic’s: "Failed to find a Flowsheet row by given codes"
    try:
        issues = resp_json.get("issue") or []
        for it in issues:
            diag = (it.get("diagnostics") or "").lower()
            if "flowsheet row" in diag:
                return True
    except Exception:
        pass
    return False


@dataclass
class VitalSpec:
    loinc: str
    display: str
    unit: str
    ucum: str
    # (min, max, rounding_digits)
    rng: Tuple[float, float, int]


def rand_value(spec: VitalSpec) -> float:
    lo, hi, nd = spec.rng
    v = random.uniform(lo, hi)
    if nd == 0:
        return float(int(round(v)))
    return round(v, nd)


def main() -> int:
    observation_url = normalize_resource_url(FHIR_BASE, "Observation")
    headers = get_headers()

    # Common vitals (may or may not be mapped in your Epic)
    vitals: List[VitalSpec] = [
        VitalSpec("2708-6", "Oxygen saturation in Arterial blood by Pulse oximetry", "%",   "%",     (90, 100, 0)),
        VitalSpec("8867-4", "Heart rate",                                             "/min","/min", (55, 130, 0)),
        VitalSpec("8310-5", "Body temperature",                                        "Cel", "Cel",  (36.0, 39.2, 1)),
        VitalSpec("9279-1", "Respiratory rate",                                        "/min","/min", (10, 30, 0)),
        VitalSpec("8480-6", "Systolic blood pressure",                                 "mm[Hg]","mm[Hg]", (85, 160, 0)),
        VitalSpec("8462-4", "Diastolic blood pressure",                                "mm[Hg]","mm[Hg]", (45, 100, 0)),
    ]

    enabled = {v.loinc: True for v in vitals}

    print("Posting random vitals every", INTERVAL_SECONDS, "seconds")
    print("Observation endpoint:", observation_url)
    print("Patient:", PATIENT_REF)
    print("Encounter:", ENCOUNTER_REF)

    while True:
        # refresh token if needed (simple strategy: retry once on 401)
        for spec in vitals:
            if not enabled.get(spec.loinc, False):
                continue

            value = rand_value(spec)
            payload = build_observation(
                patient_ref=PATIENT_REF,
                encounter_ref=ENCOUNTER_REF,
                loinc_code=spec.loinc,
                display=spec.display,
                value=value,
                unit=spec.unit,
                ucum_code=spec.ucum,
            )

            try:
                resp = requests.post(observation_url, headers=headers, json=payload, timeout=TIMEOUT_SECONDS)
                if resp.status_code == 401:
                    headers = get_headers()
                    resp = requests.post(observation_url, headers=headers, json=payload, timeout=TIMEOUT_SECONDS)

                ok = resp.status_code in (200, 201)
                print(
                    f"[{datetime.now().isoformat(timespec='seconds')}] "
                    f"POST {resp.status_code} ok={ok} loinc={spec.loinc} value={value} id={payload['id']}"
                )

                if not ok:
                    try:
                        data = resp.json()
                    except Exception:
                        data = {"raw": resp.text}

                    # Auto-disable unmapped vitals
                    if isinstance(data, dict) and is_flowsheet_row_error(data):
                        enabled[spec.loinc] = False
                        print(f"Disabling loinc={spec.loinc} (not mapped to flowsheet row in this Epic env).")

                    print(json.dumps(data, indent=2) if isinstance(data, dict) else str(data))

            except Exception as e:
                print("POST failed:", repr(e))

        time.sleep(INTERVAL_SECONDS)

    # unreachable
    # return 0


if __name__ == "__main__":
    raise SystemExit(main())
