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

import argparse
import os
import shutil
from pathlib import Path


def parse_env(path: Path) -> dict[str, str]:
    out: dict[str, str] = {}
    if not path.exists():
        return out
    for raw in path.read_text().splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            continue
        key, value = line.split("=", 1)
        out[key.strip()] = value.strip().strip('"').strip("'")
    return out


def abs_from(base: Path, raw: str, fallback: str) -> Path:
    candidate = (raw or fallback).strip()
    if not candidate:
        candidate = fallback
    p = Path(candidate).expanduser()
    if not p.is_absolute():
        p = (base / p).resolve()
    return p


def write_unit(path: Path, content: str) -> None:
    path.write_text(content.strip() + "\n")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--stack-env", default="stack.env")
    parser.add_argument(
        "--output-dir", default=str(Path.home() / ".config/containers/systemd")
    )
    args = parser.parse_args()

    repo_root = Path(__file__).resolve().parents[2]
    stack_env_path = abs_from(repo_root, args.stack_env, "stack.env")
    env = parse_env(stack_env_path)
    stack_id = str(env.get("GIA_STACK_ID") or env.get("STACK_ID") or "").strip()
    stack_id = "".join(
        ch if (ch.isalnum() or ch in "._-") else "-" for ch in stack_id
    ).strip("-")

    def with_stack(base: str) -> str:
        return f"{base}_{stack_id}" if stack_id else base

    unit_prefix = f"gia-{stack_id}" if stack_id else "gia"
    pod_ref = f"{unit_prefix}.pod"
    target_ref = f"{unit_prefix}.target"
    stack_offset_raw = str(env.get("GIA_STACK_PORT_OFFSET") or "").strip()
    if stack_offset_raw:
        try:
            stack_port_offset = max(0, int(stack_offset_raw))
        except Exception:
            stack_port_offset = 0
    elif stack_id:
        stack_port_offset = (sum(ord(ch) for ch in stack_id) % 500) + 1
    else:
        stack_port_offset = 0
    app_port = int(env.get("APP_PORT") or (5006 + stack_port_offset))
    signal_public_port = int(
        env.get("SIGNAL_PUBLIC_PORT") or (8080 + stack_port_offset)
    )

    repo_dir = abs_from(repo_root, env.get("REPO_DIR", "."), ".")
    host_uid = int(os.getuid())
    host_gid = int(os.getgid())
    app_db_file = abs_from(
        repo_root, env.get("APP_DATABASE_FILE", "./db.sqlite3"), "./db.sqlite3"
    )
    app_db_basename = app_db_file.name
    sqlite_data_dir = abs_from(
        repo_root,
        env.get("QUADLET_SQLITE_DATA_DIR", "./.podman/gia_sqlite_data"),
        "./.podman/gia_sqlite_data",
    )
    host_db_file = (sqlite_data_dir / app_db_basename).resolve()
    app_db_path_in_container = f"/conf/{app_db_basename}"

    redis_data_dir = abs_from(
        repo_root,
        env.get("QUADLET_REDIS_DATA_DIR", "./.podman/gia_redis_data"),
        "./.podman/gia_redis_data",
    )
    whatsapp_data_dir = abs_from(
        repo_root,
        env.get("QUADLET_WHATSAPP_DATA_DIR", "./.podman/gia_whatsapp_data"),
        "./.podman/gia_whatsapp_data",
    )

    vrun_dir = Path("/code/vrun") / stack_id if stack_id else Path("/code/vrun")
    signal_cli_dir = (repo_dir / "signal-cli-config").resolve()
    uwsgi_ini = (repo_dir / "docker" / "uwsgi.ini").resolve()
    redis_conf = (repo_dir / "docker" / "redis.conf").resolve()

    for p in (
        redis_data_dir,
        whatsapp_data_dir,
        sqlite_data_dir,
        vrun_dir,
        signal_cli_dir,
    ):
        p.mkdir(parents=True, exist_ok=True)
    sqlite_data_dir.chmod(0o777)
    if (
        app_db_file.resolve() != host_db_file
        and app_db_file.exists()
        and not host_db_file.exists()
    ):
        shutil.copy2(app_db_file, host_db_file)
    host_db_file.touch(exist_ok=True)
    host_db_file.chmod(0o666)

    out_dir = Path(args.output_dir).expanduser().resolve()
    out_dir.mkdir(parents=True, exist_ok=True)

    env_file = stack_env_path

    pod_unit = f"""
[Unit]
Description=GIA Pod

[Pod]
PodName={with_stack('gia')}
PublishPort={app_port}:8000
PublishPort={signal_public_port}:8080

[Install]
WantedBy=default.target
"""

    target_unit = f"""
[Unit]
Description=GIA Stack Target
Wants={unit_prefix}-redis.service {unit_prefix}-signal.service {unit_prefix}-migration.service {unit_prefix}-collectstatic.service {unit_prefix}-app.service {unit_prefix}-asgi.service {unit_prefix}-ur.service {unit_prefix}-scheduling.service
After={unit_prefix}-redis.service {unit_prefix}-signal.service {unit_prefix}-migration.service {unit_prefix}-collectstatic.service

[Install]
WantedBy=default.target
"""

    redis_unit = f"""
[Unit]
Description=GIA Redis
PartOf={target_ref}
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/library/redis:latest
ContainerName={with_stack('redis_gia')}
Pod={pod_ref}
Volume={redis_conf}:/etc/redis.conf:ro
Volume={redis_data_dir}:/data
Volume={vrun_dir}:/var/run
Exec=redis-server /etc/redis.conf

[Service]
Restart=always
RestartSec=2

[Install]
WantedBy={target_ref}
"""

    signal_unit = f"""
[Unit]
Description=GIA Signal API
PartOf={target_ref}
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/bbernhard/signal-cli-rest-api:latest
ContainerName={with_stack('signal')}
Pod={pod_ref}
Volume={signal_cli_dir}:/home/.local/share/signal-cli
Environment=MODE=native

[Service]
Restart=always
RestartSec=2

[Install]
WantedBy={target_ref}
"""

    def gia_container(
        name: str,
        container_name: str,
        command: str,
        include_uwsgi: bool,
        include_whatsapp: bool,
        after: str,
        requires: str,
        one_shot: bool = False,
    ) -> str:
        lines = [
            "[Unit]",
            f"Description={name}",
            f"PartOf={target_ref}",
            f"After={after}",
            f"Requires={requires}",
            "",
            "[Container]",
            "Image=localhost/xf/gia:prod",
            f"ContainerName={container_name}",
            f"Pod={pod_ref}",
            f"User={host_uid}:{host_gid}",
            f"EnvironmentFile={env_file}",
            "Environment=SIGNAL_HTTP_URL=http://localhost:8080",
            f"Environment=APP_DATABASE_PATH={app_db_path_in_container}",
            f"Volume={repo_dir}:/code",
            f"Volume={sqlite_data_dir}:/conf",
            f"Volume={vrun_dir}:/var/run",
        ]
        if include_uwsgi:
            lines.append(f"Volume={uwsgi_ini}:/conf/uwsgi.ini")
        if include_whatsapp:
            lines.append(
                f"Volume={whatsapp_data_dir}:{env.get('WHATSAPP_DB_DIR', '/var/tmp/whatsapp')}"
            )
        lines.append(f"Exec={command}")
        lines.extend(["", "[Service]"])
        if one_shot:
            lines.extend(
                [
                    "Type=oneshot",
                    "RemainAfterExit=yes",
                    "TimeoutStartSec=0",
                    f"ExecStartPre=/bin/sh -c 'for i in $(seq 1 60); do [ -S {vrun_dir}/gia-redis.sock ] && exit 0; sleep 1; done; exit 1'",
                ]
            )
        else:
            lines.extend(
                [
                    "Restart=always",
                    "RestartSec=2",
                ]
            )
        lines.extend(["", "[Install]", "WantedBy=gia.target"])
        lines[-1] = f"WantedBy={target_ref}"
        return "\n".join(lines)

    migration_unit = gia_container(
        "GIA Migration",
        with_stack("migration_gia"),
        "sh -c '. /venv/bin/activate && python manage.py migrate --noinput'",
        include_uwsgi=False,
        include_whatsapp=False,
        after=f"{unit_prefix}-redis.service {unit_prefix}-signal.service",
        requires=f"{unit_prefix}-redis.service {unit_prefix}-signal.service",
        one_shot=True,
    )

    collectstatic_unit = gia_container(
        "GIA Collectstatic",
        with_stack("collectstatic_gia"),
        "sh -c '. /venv/bin/activate && python manage.py collectstatic --noinput'",
        include_uwsgi=False,
        include_whatsapp=False,
        after=f"{unit_prefix}-migration.service",
        requires=f"{unit_prefix}-migration.service",
        one_shot=True,
    )

    app_unit = gia_container(
        "GIA App",
        with_stack("gia"),
        'sh -c \'if [ \\"$OPERATION\\" = \\"uwsgi\\" ] ; then . /venv/bin/activate && uwsgi --ini /conf/uwsgi.ini ; else . /venv/bin/activate && exec python manage.py runserver 0.0.0.0:8000; fi\'',
        include_uwsgi=True,
        include_whatsapp=True,
        after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
        requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
    )

    asgi_unit = gia_container(
        "GIA ASGI",
        with_stack("asgi_gia"),
        "sh -c 'rm -f /var/run/asgi-gia.sock && . /venv/bin/activate && python -m pip install --disable-pip-version-check -q uvicorn && python -m uvicorn app.asgi:application --uds /var/run/asgi-gia.sock --workers 1'",
        include_uwsgi=False,
        include_whatsapp=True,
        after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
        requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
    )

    ur_unit = gia_container(
        "GIA Unified Router",
        with_stack("ur_gia"),
        "sh -c '. /venv/bin/activate && python manage.py ur'",
        include_uwsgi=True,
        include_whatsapp=True,
        after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
        requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
    )

    scheduling_unit = gia_container(
        "GIA Scheduling",
        with_stack("scheduling_gia"),
        "sh -c '. /venv/bin/activate && python manage.py scheduling'",
        include_uwsgi=True,
        include_whatsapp=False,
        after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
        requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
    )

    write_unit(out_dir / f"{unit_prefix}.pod", pod_unit)
    write_unit(out_dir / f"{unit_prefix}.target", target_unit)
    write_unit(out_dir / f"{unit_prefix}-redis.container", redis_unit)
    write_unit(out_dir / f"{unit_prefix}-signal.container", signal_unit)
    write_unit(out_dir / f"{unit_prefix}-migration.container", migration_unit)
    write_unit(out_dir / f"{unit_prefix}-collectstatic.container", collectstatic_unit)
    write_unit(out_dir / f"{unit_prefix}-app.container", app_unit)
    write_unit(out_dir / f"{unit_prefix}-asgi.container", asgi_unit)
    write_unit(out_dir / f"{unit_prefix}-ur.container", ur_unit)
    write_unit(out_dir / f"{unit_prefix}-scheduling.container", scheduling_unit)

    print(f"Wrote Quadlet units to: {out_dir}")
    return 0


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