"""Plex Management TUI — backend helpers for running shell scripts."""

from __future__ import annotations

import asyncio
import os
import shlex
from dataclasses import dataclass, field
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent.parent  # /home/…/shell/plex


@dataclass
class CommandResult:
    returncode: int
    stdout: str
    stderr: str
    command: str

    @property
    def ok(self) -> bool:
        return self.returncode == 0

    @property
    def output(self) -> str:
        text = self.stdout
        if self.stderr:
            text += "\n" + self.stderr
        return text.strip()


def _script(name: str) -> str:
    """Return absolute path to a script in the plex directory."""
    return str(SCRIPT_DIR / name)


# ── Ansi stripping ──────────────────────────────────────────────────────

import re

_ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")


def strip_ansi(text: str) -> str:
    return _ANSI_RE.sub("", text)


# ── Async command runner ────────────────────────────────────────────────

async def run_command(
    cmd: str | list[str],
    *,
    sudo: bool = False,
    timeout: int = 300,
) -> CommandResult:
    """Run a shell command asynchronously and return the result."""
    if isinstance(cmd, list):
        shell_cmd = " ".join(shlex.quote(c) for c in cmd)
    else:
        shell_cmd = cmd

    if sudo:
        shell_cmd = f"sudo {shell_cmd}"

    env = os.environ.copy()
    env["TERM"] = "dumb"  # suppress colour in child scripts

    try:
        proc = await asyncio.create_subprocess_shell(
            shell_cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            stdin=asyncio.subprocess.DEVNULL,
            env=env,
        )
        stdout_bytes, stderr_bytes = await asyncio.wait_for(
            proc.communicate(), timeout=timeout
        )
        return CommandResult(
            returncode=proc.returncode or 0,
            stdout=strip_ansi(stdout_bytes.decode(errors="replace")),
            stderr=strip_ansi(stderr_bytes.decode(errors="replace")),
            command=shell_cmd,
        )
    except asyncio.TimeoutError:
        return CommandResult(
            returncode=-1,
            stdout="",
            stderr=f"Command timed out after {timeout}s",
            command=shell_cmd,
        )
    except Exception as exc:
        return CommandResult(
            returncode=-1,
            stdout="",
            stderr=str(exc),
            command=shell_cmd,
        )


# ── Sudo helpers ────────────────────────────────────────────────────────

_SUDO_FAIL_PATTERNS = [
    "sudo: a password is required",
    "sudo: a terminal is required",
    "sudo: no tty present",
]


def is_sudo_failure(result: CommandResult) -> bool:
    """Return True if the command failed because of missing sudo credentials."""
    if result.ok:
        return False
    text = (result.stdout + " " + result.stderr).lower()
    return any(p in text for p in _SUDO_FAIL_PATTERNS)


async def check_sudo_cached() -> bool:
    """Check whether sudo credentials are currently cached (no password needed)."""
    try:
        proc = await asyncio.create_subprocess_exec(
            "sudo", "-n", "true",
            stdin=asyncio.subprocess.DEVNULL,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL,
        )
        await proc.wait()
        return proc.returncode == 0
    except Exception:
        return False


async def cache_sudo(password: str) -> bool:
    """Cache sudo credentials by validating the given password."""
    try:
        proc = await asyncio.create_subprocess_exec(
            "sudo", "-S", "-v",
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL,
        )
        await proc.communicate(input=(password + "\n").encode())
        return proc.returncode == 0
    except Exception:
        return False


# ── High-level operations (each returns a CommandResult) ────────────────

# Service management  ─────────────────────────────────────────────────────

async def plex_start() -> CommandResult:
    return await run_command(f"bash {_script('plex.sh')} start")


async def plex_stop() -> CommandResult:
    return await run_command(f"bash {_script('plex.sh')} stop")


async def plex_restart() -> CommandResult:
    return await run_command(f"bash {_script('plex.sh')} restart")


async def plex_status() -> CommandResult:
    return await run_command(f"bash {_script('plex.sh')} status")


# Library scanning  ───────────────────────────────────────────────────────

async def library_list() -> CommandResult:
    return await run_command(f"bash {_script('scan-plex-libraries.sh')} list")


async def library_scan(section_id: str = "") -> CommandResult:
    cmd = f"bash {_script('scan-plex-libraries.sh')} scan"
    if section_id:
        cmd += f" {shlex.quote(section_id)}"
    return await run_command(cmd)


async def library_refresh(section_id: str = "", force: bool = False) -> CommandResult:
    cmd = f"bash {_script('scan-plex-libraries.sh')} refresh"
    if section_id:
        cmd += f" {shlex.quote(section_id)}"
    else:
        cmd += ' ""'
    if force:
        cmd += " true"
    return await run_command(cmd)


async def library_analyze(section_id: str = "") -> CommandResult:
    cmd = f"bash {_script('scan-plex-libraries.sh')} analyze"
    if section_id:
        cmd += f" {shlex.quote(section_id)}"
    return await run_command(cmd)


# Backup  ─────────────────────────────────────────────────────────────────

async def backup_run(auto_repair: bool = False) -> CommandResult:
    cmd = f"bash {_script('backup-plex.sh')} --non-interactive"
    if auto_repair:
        cmd += " --auto-repair"
    return await run_command(cmd, timeout=600)


async def backup_integrity_check() -> CommandResult:
    return await run_command(
        f"bash {_script('backup-plex.sh')} --check-integrity --non-interactive"
    )


async def backup_validate(latest_only: bool = True) -> CommandResult:
    cmd = f"bash {_script('validate-plex-backups.sh')}"
    if latest_only:
        cmd += " --latest"
    return await run_command(cmd)


async def backup_validate_report() -> CommandResult:
    return await run_command(f"bash {_script('validate-plex-backups.sh')} --report")


async def backup_list() -> CommandResult:
    return await run_command(f"bash {_script('plex.sh')} backups")


async def backup_builtin_status() -> CommandResult:
    return await run_command(f"bash {_script('check-plex-builtin-backups.sh')}")


async def backup_builtin_detailed() -> CommandResult:
    return await run_command(f"bash {_script('check-plex-builtin-backups.sh')} --detailed")


# Monitoring  ──────────────────────────────────────────────────────────────

async def monitor_dashboard() -> CommandResult:
    return await run_command(f"bash {_script('monitor-plex-backup.sh')}")


# Database management  ────────────────────────────────────────────────────

async def db_check() -> CommandResult:
    return await run_command(f"bash {_script('plex-db-manager.sh')} check")


async def db_repair_gentle() -> CommandResult:
    return await run_command(f"bash {_script('plex-db-manager.sh')} repair --gentle")


async def db_repair_force() -> CommandResult:
    return await run_command(f"bash {_script('plex-db-manager.sh')} repair --force")


async def db_cleanup(dry_run: bool = True) -> CommandResult:
    cmd = f"bash {_script('cleanup-plex-databases.sh')}"
    if dry_run:
        cmd += " --dry-run"
    cmd += " --verbose"
    return await run_command(cmd)


async def db_install_dbrepair() -> CommandResult:
    return await run_command(f"bash {_script('plex.sh')} install-dbrepair")


# Recovery  ────────────────────────────────────────────────────────────────

async def nuclear_recovery_dry_run() -> CommandResult:
    return await run_command(
        f"bash {_script('nuclear-plex-recovery.sh')} --dry-run"
    )


async def nuclear_recovery_auto() -> CommandResult:
    return await run_command(
        f"bash {_script('nuclear-plex-recovery.sh')} --auto", timeout=600
    )


async def nuclear_recovery_verify() -> CommandResult:
    return await run_command(
        f"bash {_script('nuclear-plex-recovery.sh')} --verify-only"
    )


async def validate_recovery(mode: str = "--quick") -> CommandResult:
    return await run_command(
        f"bash {_script('validate-plex-recovery.sh')} {mode}"
    )


# Queries  ─────────────────────────────────────────────────────────────────

async def recent_additions(days: int = 7) -> CommandResult:
    return await run_command(
        f"bash {_script('plex-recent-additions.sh')} recent {days}"
    )


async def library_stats() -> CommandResult:
    return await run_command(f"bash {_script('plex-recent-additions.sh')} stats")


async def media_counts() -> CommandResult:
    return await run_command(f"bash {_script('plex-recent-additions.sh')} count")


async def list_libraries_query() -> CommandResult:
    return await run_command(f"bash {_script('plex-recent-additions.sh')} libraries")


async def custom_query(sql: str) -> CommandResult:
    return await run_command(
        f"bash {_script('plex-recent-additions.sh')} custom {shlex.quote(sql)}"
    )


# Testing  ─────────────────────────────────────────────────────────────────

async def run_tests_quick() -> CommandResult:
    return await run_command(
        f"bash {_script('test-plex-backup.sh')} --quick", timeout=120
    )


async def run_tests_unit() -> CommandResult:
    return await run_command(
        f"bash {_script('test-plex-backup.sh')} --unit", timeout=300
    )


async def run_tests_integration() -> CommandResult:
    return await run_command(
        f"bash {_script('integration-test-plex.sh')} --quick", timeout=300
    )


async def run_tests_cleanup() -> CommandResult:
    return await run_command(
        f"bash {_script('test-plex-backup.sh')} --cleanup"
    )
