"""Tutorial 201: register a spreadsheet, extract data twice, compare named_cells JSON locally."""

import json
import os
import sys
import time
from pathlib import Path

from istari_digital_client import Client, Configuration
from istari_digital_client.exceptions import ApiException, ServiceException
from urllib3.exceptions import MaxRetryError


def _on_uncaught(exc_type, exc, tb):
    if issubclass(exc_type, MaxRetryError):
        print("\nCannot reach the Istari Digital Platform.")
        print("Check ISTARI_REGISTRY_URL and your ISTARI_PERSONAL_ACCESS_TOKEN, then try again.")
        sys.exit(1)
    sys.__excepthook__(exc_type, exc, tb)


sys.excepthook = _on_uncaught


def wait_for_job(client, job, interval=5):
    """Poll a job until it reaches a terminal state, printing the status each loop.
    Returns the refreshed Job; raises if it ends in Failed or Canceled.
    Transient service errors (5xx) are retried silently."""
    while True:
        try:
            job = client.get_job(job.id)
        except ServiceException:
            time.sleep(interval)
            continue
        label = getattr(job.status.name, "value", job.status.name)
        print(f"  status: {label}")
        if label == "Completed":
            return job
        if label in ("Failed", "Canceled"):
            raise RuntimeError(f"Job {job.id} ended in state {label}.")
        time.sleep(interval)

baseline_path = Path("Group3-UAS-Requirements.xlsx")
revised_path = Path("Group3-UAS-Requirements-v2.xlsx")

assert baseline_path.is_file(), (
    f"Expected {baseline_path.name!r} in {str(Path.cwd())!r}. "
    "Download the sample from the Python Client 201 tutorial, save it next to this script, then run again."
)
assert revised_path.is_file(), (
    f"Expected {revised_path.name!r} in {str(Path.cwd())!r}. "
    "Download the v2 sample from the same tutorial, same folder, then run again."
)

registry_url = (os.environ.get("ISTARI_REGISTRY_URL") or "").strip()
token = (os.environ.get("ISTARI_PERSONAL_ACCESS_TOKEN") or "").strip()

assert registry_url, (
    "ISTARI_REGISTRY_URL is missing or empty. In the platform: Settings → Developer Settings, "
    "copy Registry URL. In this terminal: export ISTARI_REGISTRY_URL='https://...' "
    "then run: python tutorial201.py"
)
assert token, (
    "ISTARI_PERSONAL_ACCESS_TOKEN is missing or empty. Under Developer Settings, create or copy a token, then "
    "in this terminal: export ISTARI_PERSONAL_ACCESS_TOKEN='...'  and run: python tutorial201.py"
)

# 1. Connect
client = Client(
    Configuration(
        registry_url=registry_url,
        registry_auth_token=token,
    )
)

# 2. Health check — confirms the platform is reachable and your token is accepted.
try:
    report = client.readiness_check()  # or client.liveness_check() for a lighter probe
except (ApiException, MaxRetryError) as e:
    print(f"\nCannot reach the Istari Digital Platform ({type(e).__name__}).")
    print("Check ISTARI_REGISTRY_URL and your ISTARI_PERSONAL_ACCESS_TOKEN, then try again.")
    sys.exit(1)
print(report)
if not report.healthy:
    raise RuntimeError(f"Platform reports unhealthy: {report}")

# 3. Register the first spreadsheet (same as uploading in Files)
model = client.add_model(path=baseline_path)
first_rev = model.file.revisions[0]
print(f"Model registered: {first_rev.name}")
print(f"  Model ID:     {model.id}")
print(f"  Revision ID:  {first_rev.id}")

# 4. First extraction — Open Spreadsheet → @istari:extract
job1 = client.add_job(
    model_id=model.id,
    function="@istari:extract",
    tool_name="open_spreadsheet",
)
print(f"\nJob: {job1.id}")
job1 = wait_for_job(client, job1)
print("First extraction done.")

# 5. Inspect what job 1 produced and capture the named_cells revision.
#    Each Product points to the exact FileRevision the agent wrote (race-safe).
products_1 = job1.revision.products or []
print(f"\nResources produced by job 1 ({len(products_1)} items):")
named_cells_rev1 = None
for p in products_1:
    rev = p.revision
    print(f"  - {rev.name}  (file={rev.file_id}  rev={rev.id})")
    if rev.name and "named_cells" in rev.name:
        named_cells_rev1 = rev

if named_cells_rev1 is None:
    raise RuntimeError("No named_cells.json was produced by the first extraction job.")

path_named_cells_1 = Path("named_cells_rev1.json")
path_named_cells_1.write_bytes(named_cells_rev1.read_bytes())
print(f"\nSaved {path_named_cells_1.resolve()}  (rev {named_cells_rev1.id})")

# 6. Add the second spreadsheet as a new version of the same file
model_with_2nd_rev = client.update_model(model_id=model.id, path=revised_path)
new_rev = model_with_2nd_rev.file.revisions[-1]
print("\nSecond file uploaded (new revision).")
print(f"  Model ID:     {model_with_2nd_rev.id}  (same as before)")
print(f"  Revision ID:  {new_rev.id}")

# 7. Second extraction on the current revision
job2 = client.add_job(
    model_id=model.id,
    function="@istari:extract",
    tool_name="open_spreadsheet",
)
print(f"\nJob: {job2.id}")
job2 = wait_for_job(client, job2)
print("Second extraction done.")

# 8. Same pattern for job 2
products_2 = job2.revision.products or []
print(f"\nResources produced by job 2 ({len(products_2)} items):")
named_cells_rev2 = None
for p in products_2:
    rev = p.revision
    print(f"  - {rev.name}  (file={rev.file_id}  rev={rev.id})")
    if rev.name and "named_cells" in rev.name:
        named_cells_rev2 = rev

if named_cells_rev2 is None:
    raise RuntimeError("No named_cells.json was produced by the second extraction job.")

path_named_cells_2 = Path("named_cells_rev2.json")
path_named_cells_2.write_bytes(named_cells_rev2.read_bytes())
print(f"\nSaved {path_named_cells_2.resolve()}  (rev {named_cells_rev2.id})")

# 9. Compare: list named-cell value changes between the two extractions
cells_1 = {c["name"]: c.get("value") for c in json.loads(path_named_cells_1.read_text(encoding="utf-8"))}
cells_2 = {c["name"]: c.get("value") for c in json.loads(path_named_cells_2.read_text(encoding="utf-8"))}
changes = sorted(
    (name, cells_1.get(name), cells_2.get(name))
    for name in set(cells_1) | set(cells_2)
    if cells_1.get(name) != cells_2.get(name)
)


print("\n--- Summary (file and revisions from the platform) ---\n")
print(f"Named parameters (named_cells) for {first_rev.name!r} — model {model.id}")
print(f"  from: revision {first_rev.id} · {first_rev.created.isoformat(timespec='seconds')} · user {first_rev.created_by_id}")
print(f"  to:   revision {new_rev.id} · {new_rev.created.isoformat(timespec='seconds')} · user {new_rev.created_by_id}")

print(f"\nChange detail ({len(changes)} named cell(s) changed):")
if not changes:
    print("  (no value changes)")
for name, before, after in changes:
    print(f"  - {name}: {before} → {after}")
