from __future__ import annotations

import json
import os
import shlex
import shutil
import signal
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path

from app.config import BASE_DIR, CRON_LOG, SCHEDULER_LOG, SCHEDULER_PID
from app.db import query_one


class SchedulerService:
    def cron_expression(self) -> str:
        return '* * * * *'

    def cron_command(self) -> str:
        python_exe = str(Path(sys.executable).resolve())
        worker = str((BASE_DIR / 'worker.py').resolve())
        log = str(CRON_LOG.resolve())
        return f'{shlex.quote(python_exe)} {shlex.quote(worker)} >> {shlex.quote(log)} 2>&1'

    def status(self) -> dict:
        row = query_one('SELECT * FROM cron_status WHERE id=1')
        if not row or not row['last_run_at']:
            return {'status': 'not_configured', 'last_run': None, 'next_expected': None, 'last_success': None, 'last_error': row['last_error'] if row else None}
        try:
            last_run = datetime.fromisoformat(row['last_run_at'])
            now = datetime.now(timezone.utc)
            interval = int(row['expected_interval_seconds'])
            age = (now - last_run).total_seconds()
            status = 'running' if age <= max(180, interval * 3) else 'stopped'
            next_expected = (last_run + timedelta(seconds=interval)).isoformat(timespec='seconds')
        except Exception:
            status = 'stopped'
            next_expected = None
        return {'status': status, 'last_run': row['last_run_at'], 'next_expected': next_expected, 'last_success': row['last_success_at'], 'last_error': row['last_error']}

    def _pid_alive(self, pid: int) -> bool:
        if pid <= 0:
            return False
        try:
            os.kill(pid, 0)
        except ProcessLookupError:
            return False
        except PermissionError:
            return True
        proc_cmdline = Path(f'/proc/{pid}/cmdline')
        if proc_cmdline.exists():
            try:
                return 'scheduler_daemon.py' in proc_cmdline.read_bytes().decode('utf-8', 'ignore')
            except OSError:
                return False
        return True

    def background_status(self) -> dict:
        if not SCHEDULER_PID.exists():
            return {'status': 'stopped', 'pid': None}
        try:
            data = json.loads(SCHEDULER_PID.read_text(encoding='utf-8'))
            pid = int(data.get('pid', 0))
            if self._pid_alive(pid):
                return {'status': 'running', 'pid': pid, 'started_at': data.get('started_at')}
        except Exception:
            pass
        try:
            SCHEDULER_PID.unlink()
        except OSError:
            pass
        return {'status': 'stopped', 'pid': None}

    def start_background(self) -> dict:
        current = self.background_status()
        if current['status'] == 'running':
            return {'ok': True, 'message': 'Background scheduler is already running.', **current}
        daemon = BASE_DIR / 'scheduler_daemon.py'
        SCHEDULER_LOG.parent.mkdir(parents=True, exist_ok=True)
        try:
            log_handle = open(SCHEDULER_LOG, 'a', encoding='utf-8')
            proc = subprocess.Popen(
                [sys.executable, str(daemon)],
                cwd=str(BASE_DIR),
                stdin=subprocess.DEVNULL,
                stdout=log_handle,
                stderr=subprocess.STDOUT,
                start_new_session=True,
                close_fds=True,
            )
            log_handle.close()
            payload = {'pid': proc.pid, 'started_at': datetime.now(timezone.utc).isoformat(timespec='seconds')}
            SCHEDULER_PID.write_text(json.dumps(payload), encoding='utf-8')
            try:
                os.chmod(SCHEDULER_PID, 0o600)
            except OSError:
                pass
            return {'ok': True, 'message': 'Background scheduler started.', **payload}
        except Exception as exc:
            return {'ok': False, 'message': str(exc)}

    def stop_background(self) -> dict:
        current = self.background_status()
        pid = current.get('pid')
        if not pid:
            return {'ok': True, 'message': 'Background scheduler is already stopped.'}
        try:
            os.kill(int(pid), signal.SIGTERM)
            try:
                SCHEDULER_PID.unlink()
            except OSError:
                pass
            return {'ok': True, 'message': 'Background scheduler stopped.'}
        except Exception as exc:
            return {'ok': False, 'message': str(exc)}

    def cpanel_uapi_available(self) -> bool:
        return bool(shutil.which('uapi'))

    def create_cpanel_cron(self) -> dict:
        uapi = shutil.which('uapi')
        if not uapi:
            return {'ok': False, 'supported': False, 'message': 'cPanel UAPI executable was not detected on this host.'}
        args = [
            uapi, '--output=json', 'Cron', 'add_line',
            f'command={self.cron_command()}', 'minute=*', 'hour=*', 'day=*', 'month=*', 'weekday=*',
        ]
        try:
            proc = subprocess.run(args, capture_output=True, text=True, timeout=15, check=False)
            if proc.returncode == 0 and 'error' not in proc.stdout.lower():
                return {'ok': True, 'supported': True, 'message': 'Cron entry created through cPanel UAPI.', 'output': proc.stdout[-2000:]}
            return {'ok': False, 'supported': True, 'message': 'cPanel UAPI returned an error.', 'output': (proc.stdout + proc.stderr)[-3000:]}
        except Exception as exc:
            return {'ok': False, 'supported': True, 'message': str(exc)}
