#!/usr/bin/env python3
"""Read-only PioneerRx Enterprise API bridge for Dose Scout private inventory.

Credentials are read from environment variables on the pharmacy workstation.
They are never included in the generated CSV or sent to Dose Scout. The bridge
only queries ItemSearch and ItemInventoryGroupGet and only retains mixed
amphetamine-salt and methylphenidate product rows.
"""

from __future__ import annotations

import argparse
import base64
import csv
from datetime import datetime, timezone
import hashlib
import io
import json
import os
import ssl
import sys
from typing import Any, Iterable
import urllib.error
import urllib.parse
import urllib.request


DEFAULT_DOSE_URL = "https://dose.checkall.app/api/private/inventory-import"
DEFAULT_ITEM_PATTERNS = (
    "%amphetamine%",
    "%methylphenidate%",
    "Adderall%",
    "Ritalin%",
)


def _required_env(name: str) -> str:
    value = os.environ.get(name, "").strip()
    if not value:
        raise RuntimeError(f"Set {name} on this workstation before running the bridge.")
    return value


def _timestamp() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")


def _signature(timestamp: str, shared_secret: str) -> str:
    salted = f"{timestamp}{shared_secret}".encode("utf-16-le")
    return base64.b64encode(hashlib.sha512(salted).digest()).decode("ascii")


def _lookup(record: dict[str, Any], *names: str) -> Any:
    folded = {str(key).casefold(): value for key, value in record.items()}
    for name in names:
        value = folded.get(name.casefold())
        if value not in (None, ""):
            return value
    return ""


def _walk_records(value: Any) -> Iterable[dict[str, Any]]:
    if isinstance(value, dict):
        yield value
        for child in value.values():
            yield from _walk_records(child)
    elif isinstance(value, list):
        for child in value:
            yield from _walk_records(child)


def _target_medication(description: str) -> str:
    text = description.upper()
    if "METHYLPHENIDATE" in text and "DEXMETHYLPHENIDATE" not in text:
        return "methylphenidate"
    if "ADDERALL" in text:
        return "amphetamine"
    if any(
        marker in text
        for marker in (
            "MIXED AMPHETAMINE",
            "AMPHETAMINE MIXED SALTS",
            "AMPHETAMINE SALT COMBO",
            "AMPHETAMINE-DEXTROAMPHETAMINE",
            "DEXTROAMPHETAMINE-AMPHETAMINE",
            "DEXTROAMPHETAMINE/AMPHETAMINE",
            "AMPHET/DEXTROAMPHET",
            "DEXTROAMP-AMPHET",
        )
    ):
        return "amphetamine"
    return ""


def _number(value: Any) -> float | None:
    if value in (None, ""):
        return None
    try:
        return float(str(value).replace(",", "").strip())
    except ValueError:
        return None


class PioneerRxClient:
    def __init__(
        self,
        *,
        api_url: str,
        api_key: str,
        shared_secret: str,
        ca_bundle: str = "",
    ) -> None:
        self.api_url = api_url.rstrip("/")
        self.api_key = api_key
        self.shared_secret = shared_secret
        self.ssl_context = ssl.create_default_context(cafile=ca_bundle or None)

    def _url(self, suffix: str) -> str:
        base = self.api_url
        if base.lower().endswith("/api/enterprise"):
            return f"{base}/{suffix.lstrip('/')}"
        return f"{base}/api/enterprise/{suffix.lstrip('/')}"

    def _headers(self) -> dict[str, str]:
        timestamp = _timestamp()
        return {
            "Accept": "application/json",
            "Content-Type": "application/json; charset=utf-8",
            "prx-api-key": self.api_key,
            "prx-timestamp": timestamp,
            "prx-signature": _signature(timestamp, self.shared_secret),
        }

    def _request(self, suffix: str, body: dict[str, Any] | None = None) -> Any:
        data = b"" if body is None else json.dumps(body).encode("utf-8")
        request = urllib.request.Request(
            self._url(suffix), data=data, headers=self._headers(), method="POST"
        )
        try:
            with urllib.request.urlopen(
                request, timeout=30, context=self.ssl_context
            ) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as exc:
            raise RuntimeError(
                f"PioneerRx rejected {suffix} with HTTP {exc.code}."
            ) from exc
        except urllib.error.URLError as exc:
            raise RuntimeError(
                "Could not reach the PioneerRx API. Check the pharmacy network, "
                "API URL, and trusted certificate."
            ) from exc

    def is_authenticated(self) -> bool:
        return self._request("IsAuthenticated") is True

    def method(self, name: str, employee_id: str, **parameters: Any) -> Any:
        collection = [{"Name": "RequestedByEmployeeID", "Value": employee_id}]
        collection.extend(
            {"Name": key, "Value": str(value)}
            for key, value in parameters.items()
            if value not in (None, "")
        )
        return self._request(
            "method/process",
            {
                "MethodName": name,
                "Version": "1.0",
                "ParameterCollection": collection,
            },
        )


def collect_inventory(
    client: PioneerRxClient,
    *,
    employee_id: str,
    store_name: str,
    store_id: str,
    patterns: Iterable[str] = DEFAULT_ITEM_PATTERNS,
) -> list[dict[str, Any]]:
    items: dict[str, dict[str, Any]] = {}
    for pattern in patterns:
        payload = client.method(
            "ItemSearch",
            employee_id,
            ItemName=pattern,
            IsActive=1,
            IsDeleted=0,
        )
        for record in _walk_records(payload.get("results", payload)):
            item_id = str(_lookup(record, "itemID")).strip()
            ndc = str(_lookup(record, "ndc", "NDC")).strip()
            name = str(
                _lookup(record, "itemName", "printName", "itemPrintName")
            ).strip()
            if item_id and ndc and _target_medication(name):
                items[item_id] = record

    observed_at = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
    rows: list[dict[str, Any]] = []
    for item_id, item in items.items():
        name = str(_lookup(item, "itemName", "printName", "itemPrintName")).strip()
        ndc = str(_lookup(item, "ndc", "NDC")).strip()
        manufacturer = str(_lookup(item, "manufacturer", "manufacture")).strip()
        payload = client.method(
            "ItemInventoryGroupGet",
            employee_id,
            ItemID=item_id,
            ReturnActiveInventoryGroupsOnly=1,
        )
        groups = [
            record
            for record in _walk_records(payload.get("results", payload))
            if str(_lookup(record, "itemID")).casefold() == item_id.casefold()
            and _lookup(record, "inventoryGroupID")
        ]
        for group in groups:
            on_hand = _number(_lookup(group, "onHandQuantity"))
            on_order = _number(_lookup(group, "onOrderQuantity"))
            if on_hand is not None and on_hand > 0:
                status = "on hand"
            elif on_order is not None and on_order > 0:
                status = "on order"
            else:
                status = "unavailable"
            rows.append(
                {
                    "store_id": store_id,
                    "store_name": store_name,
                    "ndc": ndc,
                    "description": name,
                    "manufacturer": manufacturer,
                    "on_hand": "" if on_hand is None else on_hand,
                    "order_quantity": "" if on_order is None else on_order,
                    "order_date": "",
                    "wholesaler": "",
                    "order_status": status,
                    "observed_at": observed_at,
                }
            )
    return rows


def inventory_csv(rows: list[dict[str, Any]]) -> bytes:
    fields = (
        "store_id",
        "store_name",
        "ndc",
        "description",
        "manufacturer",
        "on_hand",
        "order_quantity",
        "order_date",
        "wholesaler",
        "order_status",
        "observed_at",
    )
    output = io.StringIO(newline="")
    writer = csv.DictWriter(output, fieldnames=fields)
    writer.writeheader()
    writer.writerows(rows)
    return output.getvalue().encode("utf-8")


def push_to_dose_scout(
    raw_csv: bytes,
    *,
    destination: str,
    token: str,
    store_name: str,
    store_id: str,
) -> dict[str, Any]:
    query = urllib.parse.urlencode(
        {
            "source": "pioneerrx",
            "store_name": store_name,
            "store_id": store_id,
            "filename": f"pioneerrx-{datetime.now(timezone.utc):%Y%m%d-%H%M%S}.csv",
        }
    )
    separator = "&" if "?" in destination else "?"
    request = urllib.request.Request(
        f"{destination}{separator}{query}",
        data=raw_csv,
        method="POST",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "text/csv; charset=utf-8",
            "Accept": "application/json",
        },
    )
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            return json.loads(response.read().decode("utf-8"))
    except urllib.error.HTTPError as exc:
        raise RuntimeError(
            f"Dose Scout rejected the import with HTTP {exc.code}."
        ) from exc
    except urllib.error.URLError as exc:
        raise RuntimeError("Could not reach the private Dose Scout importer.") from exc


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Read only target inventory from PioneerRx Enterprise API."
    )
    parser.add_argument(
        "--check", action="store_true", help="Test PioneerRx authentication only."
    )
    parser.add_argument(
        "--push",
        action="store_true",
        help="Send the filtered snapshot to the private Dose Scout importer.",
    )
    args = parser.parse_args()

    try:
        client = PioneerRxClient(
            api_url=_required_env("PRX_API_URL"),
            api_key=_required_env("PRX_API_KEY"),
            shared_secret=_required_env("PRX_SHARED_SECRET"),
            ca_bundle=os.environ.get("PRX_CA_BUNDLE", "").strip(),
        )
        employee_id = _required_env("PRX_EMPLOYEE_ID")
        if not client.is_authenticated():
            raise RuntimeError("PioneerRx did not authenticate the connector.")
        if args.check:
            print("PioneerRx authentication succeeded.")
            return 0

        store_name = _required_env("PRX_STORE_NAME")
        store_id = os.environ.get("PRX_STORE_ID", "").strip()
        configured_patterns = tuple(
            value.strip()
            for value in os.environ.get("PRX_ITEM_PATTERNS", "").split(";")
            if value.strip()
        )
        rows = collect_inventory(
            client,
            employee_id=employee_id,
            store_name=store_name,
            store_id=store_id,
            patterns=configured_patterns or DEFAULT_ITEM_PATTERNS,
        )
        if not rows:
            raise RuntimeError(
                "No mixed-amphetamine-salt or methylphenidate inventory rows were found."
            )
        print(f"Prepared {len(rows)} filtered product inventory rows.")
        if not args.push:
            print("Dry run only. Add --push to send this snapshot to Dose Scout.")
            return 0

        result = push_to_dose_scout(
            inventory_csv(rows),
            destination=os.environ.get("DOSE_PRIVATE_URL", DEFAULT_DOSE_URL).strip()
            or DEFAULT_DOSE_URL,
            token=_required_env("DOSE_ADMIN_TOKEN"),
            store_name=store_name,
            store_id=store_id,
        )
        print(
            "Private import completed: "
            f"{result.get('accepted_rows', len(rows))} product rows accepted."
        )
        return 0
    except RuntimeError as exc:
        print(f"Bridge error: {exc}", file=sys.stderr)
        return 1


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