# retoor <retoor@molodetz.nl>

import json
from datetime import datetime, timezone

from devplacepy.database import db, get_table
from devplacepy.utils import generate_uid, make_combined_slug

ST_CREATED = "created"
ST_STARTING = "starting"
ST_RUNNING = "running"
ST_STOPPED = "stopped"
ST_PAUSED = "paused"
ST_CRASHED = "crashed"
ST_RESTARTING = "restarting"
ST_REMOVING = "removing"
ST_REMOVED = "removed"

DESIRED_RUNNING = "running"
DESIRED_STOPPED = "stopped"
DESIRED_PAUSED = "paused"

RESTART_POLICIES = ("never", "always", "on-failure", "unless-stopped")
METRICS_RING = 720


def now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _exists(table: str) -> bool:
    return table in db.tables


def create_instance(row: dict) -> dict:
    uid = generate_uid()
    base = {
        "uid": uid,
        "slug": make_combined_slug(row.get("name", "instance"), uid),
        "container_id": "",
        "container_ip": "",
        "container_gateway": "",
        "status": ST_CREATED,
        "exit_code": 0,
        "restart_count": 0,
        "ingress_slug": "",
        "ingress_port": 0,
        "run_as_uid": "",
        "boot_language": "none",
        "boot_script": "",
        "start_on_boot": 0,
        "started_at": "",
        "stopped_at": "",
        "created_at": now(),
        "updated_at": now(),
        "deleted_at": None,
        "deleted_by": None,
    }
    base.update(row)
    base["uid"] = uid
    get_table("instances").insert(base)
    return get_instance(uid)


def get_instance(uid: str):
    table = get_table("instances")
    if not _exists("instances"):
        return None
    return (
        table.find_one(uid=uid, deleted_at=None)
        or table.find_one(slug=uid, deleted_at=None)
        or table.find_one(name=uid, deleted_at=None)
    )


def list_instances(project_uid: str = None) -> list:
    if not _exists("instances"):
        return []
    filters = {"deleted_at": None}
    if project_uid:
        filters["project_uid"] = project_uid
    return sorted(
        get_table("instances").find(**filters),
        key=lambda r: r.get("created_at", ""),
        reverse=True,
    )


def all_instances() -> list:
    if not _exists("instances"):
        return []
    return list(get_table("instances").find(deleted_at=None))


def gc_workspaces(*, dry_run: bool = False) -> int:
    import shutil
    from pathlib import Path

    from devplacepy import config

    active = {inst["project_uid"] for inst in all_instances()}
    base = Path(config.CONTAINER_WORKSPACES_DIR)
    if not base.is_dir():
        return 0
    removed = 0
    for child in base.iterdir():
        if child.is_dir() and child.name not in active:
            if not dry_run:
                shutil.rmtree(child, ignore_errors=True)
            removed += 1
    return removed


def find_instance_by_ingress(slug: str):
    if not slug or not _exists("instances"):
        return None
    matches = list(get_table("instances").find(ingress_slug=slug, deleted_at=None))
    for instance in matches:
        if instance.get("status") == ST_RUNNING:
            return instance
    return matches[0] if matches else None


def update_instance(uid: str, changes: dict) -> None:
    get_table("instances").update({"uid": uid, "updated_at": now(), **changes}, ["uid"])


def record_activity(
    uid: str, seen_at: str, egress_bytes: int = 0, requests: int = 0
) -> None:
    from sqlalchemy import text

    if not _exists("instances"):
        return
    sql = (
        "UPDATE instances SET last_active_at = :seen_at, "
        "egress_bytes = COALESCE(egress_bytes, 0) + :egress, "
        "request_count = COALESCE(request_count, 0) + :requests "
        "WHERE uid = :uid AND deleted_at IS NULL"
    )
    params = {
        "seen_at": seen_at,
        "egress": max(0, egress_bytes),
        "requests": max(0, requests),
        "uid": uid,
    }
    with db:
        db.executable.execute(text(sql), params)


def delete_instance(uid: str, deleted_by: str = "system") -> None:
    get_table("instances").update(
        {"uid": uid, "deleted_at": now(), "deleted_by": deleted_by}, ["uid"]
    )


def record_event(
    instance: dict, event: str, actor_kind: str, actor_id: str, detail: dict = None
) -> None:
    get_table("instance_events").insert(
        {
            "uid": generate_uid(),
            "instance_uid": instance["uid"],
            "project_uid": instance.get("project_uid", ""),
            "event": event,
            "actor_kind": actor_kind,
            "actor_id": actor_id or "",
            "detail": json.dumps(detail or {}),
            "created_at": now(),
        }
    )


def list_events(instance_uid: str, limit: int = 100) -> list:
    if not _exists("instance_events"):
        return []
    rows = sorted(
        get_table("instance_events").find(instance_uid=instance_uid),
        key=lambda r: r.get("created_at", ""),
        reverse=True,
    )
    return rows[:limit]


def insert_metric(instance_uid: str, sample) -> None:
    table = get_table("instance_metrics")
    table.insert(
        {
            "uid": generate_uid(),
            "instance_uid": instance_uid,
            "ts": now(),
            "cpu_pct": sample.cpu_pct,
            "mem_bytes": sample.mem_bytes,
            "mem_pct": sample.mem_pct,
            "net_rx": sample.net_rx,
            "net_tx": sample.net_tx,
            "blk_read": sample.blk_read,
            "blk_write": sample.blk_write,
        }
    )
    rows = sorted(table.find(instance_uid=instance_uid), key=lambda r: r.get("ts", ""))
    if len(rows) > METRICS_RING:
        for old in rows[: len(rows) - METRICS_RING]:
            table.delete(uid=old["uid"])


def recent_metrics(instance_uid: str, limit: int = 120) -> list:
    if not _exists("instance_metrics"):
        return []
    rows = sorted(
        get_table("instance_metrics").find(instance_uid=instance_uid),
        key=lambda r: r.get("ts", ""),
    )
    return rows[-limit:]


def create_schedule(
    instance: dict, action: str, schedule_columns: dict, next_run_at: str
) -> dict:
    uid = generate_uid()
    get_table("instance_schedules").insert(
        {
            "uid": uid,
            "instance_uid": instance["uid"],
            "project_uid": instance.get("project_uid", ""),
            "action": action,
            "schedule_json": json.dumps(schedule_columns),
            "enabled": 1,
            "next_run_at": next_run_at,
            "last_run_at": "",
            "run_count": 0,
            "created_at": now(),
            "updated_at": now(),
            "deleted_at": None,
            "deleted_by": None,
        }
    )
    return get_table("instance_schedules").find_one(uid=uid)


def list_schedules(instance_uid: str) -> list:
    if not _exists("instance_schedules"):
        return []
    return list(
        get_table("instance_schedules").find(
            instance_uid=instance_uid, deleted_at=None
        )
    )


def due_schedules(now_iso: str) -> list:
    if not _exists("instance_schedules"):
        return []
    return [
        r
        for r in get_table("instance_schedules").find(enabled=1, deleted_at=None)
        if r.get("next_run_at") and r["next_run_at"] <= now_iso
    ]


def update_schedule(uid: str, changes: dict) -> None:
    get_table("instance_schedules").update(
        {"uid": uid, "updated_at": now(), **changes}, ["uid"]
    )


def delete_schedule(uid: str, deleted_by: str = "system") -> None:
    get_table("instance_schedules").update(
        {"uid": uid, "deleted_at": now(), "deleted_by": deleted_by}, ["uid"]
    )
