"""Framework crontab manager — collects module cron declarations into AGENTO:BEGIN/END block.

Separate from Jira's ``JIRA-SYNC:BEGIN/END`` block which manages dynamic
per-issue entries.  This module handles static, module-declared cron jobs
from ``cron.json`` companion files.
"""
from __future__ import annotations

import json
import subprocess
from dataclasses import dataclass
from pathlib import Path

from .module_loader import ModuleManifest

MARKER_BEGIN = "# AGENTO:BEGIN - Auto-generated by setup:upgrade. Do not edit manually."
MARKER_END = "# AGENTO:END"

ENV_FILE = "/opt/cron-agent/env"
ENVLOAD = f"set -a; source {ENV_FILE}; set +a"
COMMAND_TEMPLATE = f"{ENVLOAD}; cd /workspace && /opt/cron-agent/run.sh {{command}} >/dev/null 2>&1"


@dataclass
class CronJob:
    """A declared cron job from ``cron.json``."""

    name: str
    schedule: str
    command: str = ""
    raw_command: str = ""

    @property
    def full_command(self) -> str:
        if self.raw_command:
            return self.raw_command
        return COMMAND_TEMPLATE.format(command=self.command)

    def to_crontab_line(self) -> str:
        return f"# {self.name}\n{self.schedule} {self.full_command}"


def collect_cron_jobs(
    manifests: list[ModuleManifest],
    framework_crontab_path: Path | None = None,
) -> list[CronJob]:
    """Collect cron job declarations from framework + all modules."""
    jobs: list[CronJob] = []

    # Framework-level cron.json (logrotate, etc.)
    if framework_crontab_path and framework_crontab_path.exists():
        data = json.loads(framework_crontab_path.read_text())
        for entry in data.get("jobs", []):
            jobs.append(CronJob(
                name=entry["name"],
                schedule=entry["schedule"],
                command=entry.get("command", ""),
                raw_command=entry.get("raw_command", ""),
            ))

    # Module cron.json declarations
    for m in manifests:
        for entry in m.cron.get("jobs", []):
            jobs.append(CronJob(
                name=f"{m.name}/{entry['name']}",
                schedule=entry["schedule"],
                command=entry["command"],
            ))

    return jobs


def get_current_crontab() -> str:
    """Read the current crontab. Returns empty string if none set."""
    result = subprocess.run(["crontab", "-l"], capture_output=True, text=True)
    if result.returncode != 0:
        return ""
    return result.stdout


def extract_unmanaged(crontab: str) -> str:
    """Remove AGENTO:BEGIN/END block, preserve everything else (including JIRA-SYNC)."""
    lines = crontab.splitlines()
    out: list[str] = []
    skip = False
    for line in lines:
        if line == MARKER_BEGIN:
            skip = True
            continue
        if line == MARKER_END:
            skip = False
            continue
        if not skip:
            out.append(line)
    while out and out[-1] == "":
        out.pop()
    return "\n".join(out)


def build_managed_block(jobs: list[CronJob]) -> str:
    """Format cron jobs into the managed block content (without markers)."""
    if not jobs:
        return ""
    return "\n".join(j.to_crontab_line() for j in jobs)


def assemble(unmanaged: str, managed: str) -> str:
    """Combine unmanaged portion with AGENTO managed block."""
    parts: list[str] = []
    if unmanaged:
        parts.append(unmanaged)
    if managed:
        parts.append(MARKER_BEGIN)
        parts.append("")
        parts.append(managed)
        parts.append("")
        parts.append(MARKER_END)
    result = "\n".join(parts)
    if result and not result.endswith("\n"):
        result += "\n"
    return result


def install_crontab(new_crontab: str, dry_run: bool = False) -> bool:
    """Apply new crontab if it differs from current. Returns True if changed."""
    current = get_current_crontab()
    if new_crontab == current:
        return False
    if dry_run:
        return True
    subprocess.run(["crontab", "-"], input=new_crontab, text=True, check=True)
    return True
