import argparse
import json
import os
import subprocess
import time
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo


DATA_FILE = Path(
    os.getenv(
        "STAYCATION_DATA_FILE",
        Path(__file__).with_name("data") / "staycation_state.json",
    )
)
TIMEZONE = ZoneInfo(os.getenv("STAYCATION_TIMEZONE", "Europe/Amsterdam"))
SIGNAL_CLI_BIN = os.getenv("SIGNAL_CLI_BIN", "signal-cli")
SIGNAL_SENDER = os.getenv("SIGNAL_SENDER", "")
SIGNAL_RECIPIENTS = [
    item.strip()
    for item in os.getenv("SIGNAL_RECIPIENTS", "").split(",")
    if item.strip()
]
SIGNAL_GROUP_ID = os.getenv("SIGNAL_GROUP_ID", "")
SEND_HOUR = int(os.getenv("STAYCATION_SIGNAL_HOUR", "20"))
SEND_MINUTE = int(os.getenv("STAYCATION_SIGNAL_MINUTE", "0"))
CHECK_SECONDS = int(os.getenv("STAYCATION_SIGNAL_CHECK_SECONDS", "300"))
USERS = ("Ardy", "Marije", "Isis", "Ids")


def read_state():
    if not DATA_FILE.exists():
        return {}
    with DATA_FILE.open("r", encoding="utf-8-sig") as handle:
        return json.load(handle)


def write_state(state):
    DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
    with DATA_FILE.open("w", encoding="utf-8") as handle:
        json.dump(state, handle, ensure_ascii=True, indent=2)


def now_local():
    return datetime.now(TIMEZONE)


def should_try_send(current_time):
    return (current_time.hour, current_time.minute) >= (SEND_HOUR, SEND_MINUTE)


def add_idea(grouped, user, bucket, idea_name):
    if user not in grouped or not idea_name:
        return
    if idea_name not in grouped[user][bucket]:
        grouped[user][bucket].append(idea_name)


def group_changes(changes):
    grouped = {
        user: {
            "ideas_added": [],
            "ideas_updated": [],
            "voted": False,
            "veto_set": [],
            "veto_cleared": [],
        }
        for user in USERS
    }

    for change in changes:
        user = change.get("actor")
        if user not in grouped:
            continue

        kind = change.get("kind") or ""
        idea_name = str(change.get("ideaName") or "").strip()
        text = str(change.get("text") or "").strip()

        if kind == "idea_added":
            add_idea(grouped, user, "ideas_added", idea_name or text)
        elif kind == "idea_updated":
            add_idea(grouped, user, "ideas_updated", idea_name or text)
        elif kind == "vote_changed":
            grouped[user]["voted"] = True
        elif kind == "veto_set":
            add_idea(grouped, user, "veto_set", idea_name or text)
        elif kind == "veto_cleared":
            add_idea(grouped, user, "veto_cleared", idea_name or text)
    return grouped


def join_names(names):
    return ", ".join(names)


def build_message(changes):
    grouped = group_changes(changes)
    lines = [
        "Staycation update",
        "",
        "Korte stand van vandaag:",
    ]

    any_user_changes = False
    for user in USERS:
        summary = grouped[user]
        details = []
        if summary["ideas_added"]:
            details.append(f"nieuw idee: {join_names(summary['ideas_added'])}")
        if summary["ideas_updated"]:
            details.append(f"idee aangepast: {join_names(summary['ideas_updated'])}")
        if summary["voted"]:
            details.append("heeft gestemd")
        if summary["veto_set"]:
            details.append(f"veto tegen: {join_names(summary['veto_set'])}")
        if summary["veto_cleared"]:
            details.append(f"veto weggehaald bij: {join_names(summary['veto_cleared'])}")
        if details:
            any_user_changes = True
            lines.append(f"- {user}: {'; '.join(details)}.")

    if not any_user_changes:
        lines.append("- Er zijn alleen technische of planningswijzigingen geweest.")
    lines.extend(["", "Open de app voor de actuele stand: https://me.ardyduwel.net/staycation"])
    return "\n".join(lines)


def send_signal(message):
    if not SIGNAL_SENDER:
        raise RuntimeError("SIGNAL_SENDER ontbreekt.")
    if not SIGNAL_GROUP_ID and not SIGNAL_RECIPIENTS:
        raise RuntimeError("SIGNAL_GROUP_ID of SIGNAL_RECIPIENTS ontbreekt.")

    command = [SIGNAL_CLI_BIN, "-u", SIGNAL_SENDER, "send", "-m", message]
    if SIGNAL_GROUP_ID:
        command.extend(["-g", SIGNAL_GROUP_ID])
    else:
        command.extend(SIGNAL_RECIPIENTS)
    subprocess.run(command, check=True)


def dry_run_message(message):
    return "\n".join(
        [
            "DRY RUN - dit bericht zou worden verstuurd:",
            "",
            message,
        ]
    )


def run_once(force=False, dry_run=False):
    state = read_state()
    notifications = state.setdefault("notifications", {})
    changes = notifications.setdefault("pendingChanges", [])
    today = now_local().date().isoformat()

    if not changes:
        return "Geen wijzigingen om te melden."
    if notifications.get("lastSentDate") == today and not force:
        return "Vandaag is er al een Signal update verstuurd."
    if not force and not should_try_send(now_local()):
        return "Het dagelijkse verzendmoment is nog niet bereikt."

    message = build_message(changes)
    if dry_run:
        return dry_run_message(message)

    send_signal(message)
    notifications["lastSentDate"] = today
    notifications["pendingChanges"] = []
    write_state(state)
    return "Signal update verstuurd."


def run_loop():
    while True:
        try:
            print(run_once(), flush=True)
        except Exception as error:
            print(f"Signal update niet verstuurd: {error}", flush=True)
        time.sleep(CHECK_SECONDS)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--loop", action="store_true")
    parser.add_argument("--force", action="store_true")
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args()

    if args.loop:
        run_loop()
        return
    print(run_once(force=args.force, dry_run=args.dry_run))


if __name__ == "__main__":
    main()
