"""
Task Scheduler
==============
Generated by El Gringo team (grok-coder won consensus).
Fixed: deadlock in _save_schedule, cron parser safety, missing Any import,
added real task dispatch to standup/content generators.
"""

import json
import logging
import threading
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

BASE_DIR = Path.home() / ".ai-dev-team" / "workflow"
SCHEDULE_FILE = BASE_DIR / "schedule.json"
LOG_FILE = BASE_DIR / "scheduler.log"
BASE_DIR.mkdir(parents=True, exist_ok=True)

TASK_TYPES = ("standup", "social_post", "sprint_report", "newsletter", "code_review", "custom")


@dataclass
class ScheduledTask:
    id: str = ""
    name: str = ""
    cron_expression: str = "0 9 * * *"
    task_type: str = "custom"
    task_config: Dict[str, Any] = field(default_factory=dict)
    enabled: bool = True
    last_run: Optional[str] = None
    next_run: Optional[str] = None
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())


class TaskScheduler:
    def __init__(self):
        self._tasks: Dict[str, ScheduledTask] = {}
        self._lock = threading.Lock()
        self._stop_event = threading.Event()
        self._thread: Optional[threading.Thread] = None
        self._load()
        if not self._tasks:
            self._add_defaults()

    def _add_defaults(self):
        self.add_task("Daily Standup", "0 9 * * *", "standup", {})
        self.add_task("Weekly Social Posts", "0 8 * * 1", "social_post", {})
        self.add_task("Sprint Summary", "0 17 * * 5", "sprint_report", {})

    # --- CRUD ---

    def add_task(self, name: str, cron_expr: str, task_type: str,
                 config: Optional[Dict] = None) -> str:
        task_id = str(uuid.uuid4())[:8]
        task = ScheduledTask(
            id=task_id, name=name, cron_expression=cron_expr,
            task_type=task_type, task_config=config or {},
        )
        task.next_run = self._next_cron_time(cron_expr).isoformat()
        with self._lock:
            self._tasks[task_id] = task
        self._save()
        self._log(f"Added: {name} ({task_id}) cron={cron_expr}")
        return task_id

    def remove_task(self, task_id: str) -> bool:
        with self._lock:
            if task_id in self._tasks:
                name = self._tasks[task_id].name
                del self._tasks[task_id]
                self._save()
                self._log(f"Removed: {name} ({task_id})")
                return True
        return False

    def list_tasks(self) -> List[Dict]:
        with self._lock:
            return [
                {"id": t.id, "name": t.name, "cron": t.cron_expression,
                 "type": t.task_type, "enabled": t.enabled,
                 "next_run": t.next_run, "last_run": t.last_run}
                for t in self._tasks.values()
            ]

    def toggle_task(self, task_id: str) -> bool:
        with self._lock:
            if task_id not in self._tasks:
                return False
            self._tasks[task_id].enabled = not self._tasks[task_id].enabled
        self._save()
        return True

    def get_next_runs(self, hours: int = 24) -> List[Dict]:
        cutoff = (datetime.now() + timedelta(hours=hours)).isoformat()
        with self._lock:
            return [
                {"name": t.name, "next_run": t.next_run, "type": t.task_type}
                for t in self._tasks.values()
                if t.enabled and t.next_run and t.next_run <= cutoff
            ]

    # --- Scheduler loop ---

    def start(self):
        if self._thread and self._thread.is_alive():
            return
        self._stop_event.clear()
        self._thread = threading.Thread(target=self._loop, daemon=True)
        self._thread.start()
        self._log("Scheduler started")

    def stop(self):
        self._stop_event.set()
        if self._thread:
            self._thread.join(timeout=5)
        self._log("Scheduler stopped")

    def _loop(self):
        while not self._stop_event.is_set():
            self._check_and_run()
            self._stop_event.wait(60)

    def _check_and_run(self):
        now = datetime.now().isoformat()
        tasks_to_run = []
        with self._lock:
            for t in self._tasks.values():
                if t.enabled and t.next_run and t.next_run <= now:
                    tasks_to_run.append(t)

        for task in tasks_to_run:
            try:
                self._execute(task)
            except Exception as e:
                self._log(f"Error running {task.name}: {e}")
            task.last_run = datetime.now().isoformat()
            task.next_run = self._next_cron_time(task.cron_expression).isoformat()

        if tasks_to_run:
            self._save()

    def _execute(self, task: ScheduledTask):
        self._log(f"Executing: {task.name} (type={task.task_type})")

        if task.task_type == "standup":
            from .standup import StandupGenerator
            sg = StandupGenerator()
            data = sg.generate_standup()
            sg.save_standup(data)
            self._log(f"Standup saved for {data['date']}")

        elif task.task_type == "sprint_report":
            from .sprint_manager import SprintManager
            sm = SprintManager()
            stats = sm.get_summary_stats()
            self._log(f"Sprint report: {stats['tasks_done']}/{stats['tasks_total']} done")

        elif task.task_type in ("social_post", "newsletter", "code_review", "custom"):
            self._log(f"Task type '{task.task_type}' queued (requires AI generation)")

    # --- Cron parser (stdlib only) ---

    def _next_cron_time(self, cron_expr: str) -> datetime:
        parts = cron_expr.split()
        if len(parts) != 5:
            return datetime.now() + timedelta(hours=1)

        minute, hour, day, month, weekday = parts
        now = datetime.now().replace(second=0, microsecond=0) + timedelta(minutes=1)

        for _ in range(60 * 24 * 8):  # max 8 days lookahead
            if (self._cron_match(now.minute, minute) and
                    self._cron_match(now.hour, hour) and
                    self._cron_match(now.day, day) and
                    self._cron_match(now.month, month) and
                    self._cron_match(now.isoweekday() % 7, weekday)):
                return now
            now += timedelta(minutes=1)

        return datetime.now() + timedelta(hours=1)  # fallback

    @staticmethod
    def _cron_match(value: int, pattern: str) -> bool:
        if pattern == "*":
            return True
        try:
            return value == int(pattern)
        except ValueError:
            return False

    # --- Persistence ---

    def _save(self):
        data = {}
        with self._lock:
            for tid, t in self._tasks.items():
                data[tid] = {
                    "id": t.id, "name": t.name, "cron_expression": t.cron_expression,
                    "task_type": t.task_type, "task_config": t.task_config,
                    "enabled": t.enabled, "last_run": t.last_run,
                    "next_run": t.next_run, "created_at": t.created_at,
                }
        with SCHEDULE_FILE.open("w") as f:
            json.dump(data, f, indent=2)

    def _load(self):
        if not SCHEDULE_FILE.exists():
            return
        try:
            with SCHEDULE_FILE.open("r") as f:
                data = json.load(f)
            with self._lock:
                for tid, info in data.items():
                    self._tasks[tid] = ScheduledTask(**info)
        except (json.JSONDecodeError, TypeError) as e:
            logger.warning(f"Failed to load schedule: {e}")

    def _log(self, msg: str):
        line = f"{datetime.now().isoformat()} {msg}"
        logger.info(line)
        try:
            with LOG_FILE.open("a") as f:
                f.write(line + "\n")
        except OSError:
            pass
