#!/usr/bin/env python3

import argparse
import fcntl
import hashlib
import json
import os
import subprocess
import sys
import tempfile
import urllib.request
from urllib.parse import urljoin

CONFIG = "/etc/ekklesia-classroom/device.conf"
LOCKFILE = "/run/ekklesia-classroom-update.lock"


def log(message):
    print(f"[ekklesia-classroom-update] {message}", flush=True)


def read_config():
    config = {
        "UPDATE_URL": "https://updates.riverofglorymn.org",
        "CHANNEL": "stable",
    }

    if os.path.exists(CONFIG):
        with open(CONFIG, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                key, value = line.split("=", 1)
                config[key.strip()] = value.strip()

    return config


def installed_version():
    result = subprocess.run(
        ["dpkg-query", "-W", "-f=${Version}", "ekklesia-classroom"],
        capture_output=True,
        text=True,
    )

    if result.returncode != 0:
        return "0.0.0"

    return result.stdout.strip()


def version_is_newer(candidate, current):
    result = subprocess.run(
        ["dpkg", "--compare-versions", candidate, "gt", current]
    )
    return result.returncode == 0


def fetch_json(url):
    request = urllib.request.Request(
        url,
        headers={"User-Agent": "Ekklesia-Classroom-Updater/0.1"},
    )

    with urllib.request.urlopen(request, timeout=30) as response:
        return json.load(response)


def download(url, destination):
    request = urllib.request.Request(
        url,
        headers={"User-Agent": "Ekklesia-Classroom-Updater/0.1"},
    )

    with urllib.request.urlopen(request, timeout=120) as response:
        with open(destination, "wb") as f:
            while True:
                chunk = response.read(1024 * 1024)
                if not chunk:
                    break
                f.write(chunk)


def sha256(path):
    digest = hashlib.sha256()

    with open(path, "rb") as f:
        while True:
            chunk = f.read(1024 * 1024)
            if not chunk:
                break
            digest.update(chunk)

    return digest.hexdigest()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--no-reboot",
        action="store_true",
        help="Install the update but do not reboot automatically",
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="Check for an update without installing it",
    )
    args = parser.parse_args()

    with open(LOCKFILE, "w", encoding="utf-8") as lock:
        try:
            fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except BlockingIOError:
            log("Another update is already running.")
            return 0

        config = read_config()

        base_url = config["UPDATE_URL"].rstrip("/") + "/"
        channel = config["CHANNEL"]
        manifest_url = urljoin(base_url, f"{channel}/manifest.json")

        current = installed_version()

        log(f"Current version: {current}")
        log(f"Channel: {channel}")
        log(f"Checking: {manifest_url}")

        try:
            manifest = fetch_json(manifest_url)
        except Exception as exc:
            log(f"Unable to fetch update manifest: {exc}")
            return 1

        candidate = manifest["version"]

        log(f"Available version: {candidate}")

        if not version_is_newer(candidate, current):
            log("Already up to date.")
            return 0

        if args.check:
            log(f"Update available: {current} -> {candidate}")
            return 0

        package_url = urljoin(base_url, manifest["package"].lstrip("/"))
        expected_hash = manifest["sha256"].lower()

        os.makedirs("/var/cache/ekklesia-classroom", exist_ok=True)

        package_path = os.path.join(
            "/var/cache/ekklesia-classroom",
            os.path.basename(manifest["package"]),
        )

        temporary_path = package_path + ".download"

        log(f"Downloading {package_url}")

        try:
            download(package_url, temporary_path)
        except Exception as exc:
            log(f"Download failed: {exc}")
            return 1

        actual_hash = sha256(temporary_path)

        if actual_hash != expected_hash:
            log("SHA-256 verification FAILED.")
            log(f"Expected: {expected_hash}")
            log(f"Received: {actual_hash}")
            os.unlink(temporary_path)
            return 1

        log("SHA-256 verification passed.")

        os.replace(temporary_path, package_path)

        log(f"Installing {candidate}")

        result = subprocess.run(
            [
                "apt-get",
                "install",
                "-y",
                package_path,
            ]
        )

        if result.returncode != 0:
            log("Package installation failed.")
            return result.returncode

        log(f"Update to {candidate} installed successfully.")

        if manifest.get("requires_reboot", False):
            if args.no_reboot:
                log("Update requires reboot, but --no-reboot was specified.")
            else:
                log("Update requires reboot. Rebooting now.")
                subprocess.run(["systemctl", "reboot"])

        return 0


if __name__ == "__main__":
    sys.exit(main())
