#!/usr/bin/env python3
"""fenris-monitor: privileged helper for toggle, collect, and baseline operations.

This binary is the ONLY sanctioned control path for:
- enable/disable (toggle) with monitoring-period bookkeeping
- on-demand collection trigger
- baseline set/clear persistence

Polkit authorizes this binary under com.bongbetic.fenris.monitor (auth_admin).

Spec: §8.4, §8.5, §8.6, §8.7

When run as a script, uses the fenris package from the installed wheel.
"""
import argparse
import json
import os
import subprocess
import sys
import sqlite3
from datetime import datetime, timezone
from pathlib import Path

# Package dependencies are vendored independently of the host Python minor
# version.  Keep the venv fallback for the legacy development install.
VENV_DIR = Path("/opt/fenris")
if VENV_DIR.exists():
    vendor_dir = VENV_DIR / "vendor"
    if vendor_dir.is_dir():
        sys.path.insert(0, str(vendor_dir))
    else:
        site_packages = next((VENV_DIR / "lib").glob("python*/site-packages"), None)
        if site_packages:
            sys.path.insert(0, str(site_packages))

from fenris.store import init_store, get_store_path
from fenris.monitoring_periods import (
    ensure_period_open,
    close_period,
    get_open_period,
)


DEFAULT_STORE_PATH = Path("/var/lib/fenris/observations.db")


def is_root() -> bool:
    """Check if running as root."""
    return os.geteuid() == 0


def cmd_enable(args: argparse.Namespace) -> None:
    """Enable monitoring: enable timer + open monitoring period.

    Idempotent matrix (§8.6):
    - First-ever enable: opens a period at the enable moment
    - Resume with open period: no-op (gap stays inside as unknown)
    - Resume with no open period: opens a new row
    """
    store_path = getattr(args, 'store_path', DEFAULT_STORE_PATH)
    conn = init_store(store_path)
    now = datetime.now(timezone.utc)

    try:
        # Open monitoring period if none exists (§8.6)
        open_period = get_open_period(conn)
        if open_period is None:
            ensure_period_open(conn, now)
            print("Monitoring period opened at", now.isoformat())
        else:
            print("Monitoring period already open (id=%d)" % open_period["id"])

        # Enable and start the timer
        if args.now:
            result = subprocess.run(
                ["systemctl", "enable", "--now", "fenris-collect.timer"],
                capture_output=True,
                text=True,
            )
        else:
            result = subprocess.run(
                ["systemctl", "enable", "fenris-collect.timer"],
                capture_output=True,
                text=True,
            )

        if result.returncode != 0:
            print("Error enabling timer:", result.stderr, file=sys.stderr)
            sys.exit(1)

        print("Timer enabled" + (" and started" if args.now else ""))
    finally:
        conn.close()


def cmd_disable(args: argparse.Namespace) -> None:
    """Disable monitoring: disable timer + close monitoring period.

    Idempotent matrix (§8.6):
    - Pause with open period: closes it user_disabled
    - Pause otherwise: no-op
    """
    store_path = getattr(args, 'store_path', DEFAULT_STORE_PATH)
    if not store_path.exists():
        print("Error: Observation store not found at", store_path, file=sys.stderr)
        sys.exit(1)

    conn = init_store(store_path)
    now = datetime.now(timezone.utc)

    try:
        # Close monitoring period if open (§8.6)
        open_period = get_open_period(conn)
        if open_period is not None:
            close_period(conn, now, "user_disabled")
            print("Monitoring period closed (id=%d)" % open_period["id"])
        else:
            print("No open monitoring period (no-op)")

        # Disable and stop the timer
        if args.now:
            result = subprocess.run(
                ["systemctl", "disable", "--now", "fenris-collect.timer"],
                capture_output=True,
                text=True,
            )
        else:
            result = subprocess.run(
                ["systemctl", "disable", "fenris-collect.timer"],
                capture_output=True,
                text=True,
            )

        if result.returncode != 0:
            print("Error disabling timer:", result.stderr, file=sys.stderr)
            sys.exit(1)

        print("Timer disabled" + (" and stopped" if args.now else ""))
    finally:
        conn.close()


def cmd_collect(args: argparse.Namespace) -> None:
    """Trigger on-demand collection.

    Starts fenris-collect.service, blocks until exit, reports outcome.

    Spec §8.7: fenris sample routes through fenris-monitor → systemctl start,
    which blocks until the oneshot exits; outcome reported synchronously.
    """
    result = subprocess.run(
        ["systemctl", "start", "fenris-collect.service"],
        capture_output=True,
        text=True,
    )

    if result.returncode == 0:
        print("Collection completed successfully")
    else:
        print("Collection failed:", result.stderr, file=sys.stderr)
        sys.exit(1)


def cmd_baseline_set(args: argparse.Namespace) -> None:
    """Persist a baseline row after CLI-side validation.

    Spec §8.4: fenris-monitor persists CLI-validated baseline rows.
    Spec PR-14: baseline set persists through polkit-guarded helper.
    """
    store_path = getattr(args, 'store_path', DEFAULT_STORE_PATH)
    if not store_path.exists():
        print("Error: Observation store not found at", store_path, file=sys.stderr)
        sys.exit(1)

    conn = init_store(store_path)
    now = datetime.now(timezone.utc)

    try:
        # Parse and validate baseline data
        data = json.loads(args.baseline_json)

        required_fields = [
            "tbw_terabytes",
            "source_url",
            "document_revision",
            "entry_date",
            "model_string",
            "nominal_capacity_bytes",
        ]
        for field in required_fields:
            if field not in data:
                print(f"Error: Missing required field: {field}", file=sys.stderr)
                sys.exit(1)

        # One active row replaced on edit (§6.2)
        conn.execute("DELETE FROM endurance_baseline")
        conn.execute(
            """
            INSERT INTO endurance_baseline (
                tbw_terabytes, source_url, document_revision,
                entry_date, model_string, nominal_capacity_bytes,
                validated_by, verified, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                data["tbw_terabytes"],
                data["source_url"],
                data["document_revision"],
                data["entry_date"],
                data["model_string"],
                data["nominal_capacity_bytes"],
                data.get("validated_by", "user"),
                data.get("verified", False),
                now.isoformat(),
                now.isoformat(),
            ),
        )
        conn.commit()
        print("Baseline persisted")
    finally:
        conn.close()


def cmd_baseline_clear(args: argparse.Namespace) -> None:
    """Clear the endurance baseline.

    Spec PR-14: baseline clear persists through polkit-guarded helper.
    """
    store_path = getattr(args, 'store_path', DEFAULT_STORE_PATH)
    if not store_path.exists():
        print("Error: Observation store not found at", store_path, file=sys.stderr)
        sys.exit(1)

    conn = init_store(store_path)
    try:
        conn.execute("DELETE FROM endurance_baseline")
        conn.commit()
        print("Baseline cleared")
    finally:
        conn.close()


def main() -> None:
    parser = argparse.ArgumentParser(
        prog="fenris-monitor",
        description="Fenris privileged helper for toggle, collect, and baseline operations.",
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    # enable/disable
    enable_parser = subparsers.add_parser("enable", help="Enable monitoring")
    enable_parser.add_argument(
        "--now", action="store_true", help="Also start the timer immediately"
    )
    enable_parser.set_defaults(func=cmd_enable)

    disable_parser = subparsers.add_parser("disable", help="Disable monitoring")
    disable_parser.add_argument(
        "--now", action="store_true", help="Also stop the timer immediately"
    )
    disable_parser.set_defaults(func=cmd_disable)

    # collect
    collect_parser = subparsers.add_parser("collect", help="Trigger on-demand collection")
    collect_parser.set_defaults(func=cmd_collect)

    # baseline
    baseline_parser = subparsers.add_parser("baseline", help="Baseline operations")
    baseline_sub = baseline_parser.add_subparsers(dest="baseline_action", required=True)

    baseline_set = baseline_sub.add_parser("set", help="Persist baseline")
    baseline_set.add_argument("baseline_json", help="Baseline JSON data")
    baseline_set.set_defaults(func=cmd_baseline_set)

    baseline_clear = baseline_sub.add_parser("clear", help="Clear baseline")
    baseline_clear.set_defaults(func=cmd_baseline_clear)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
