"""
Sequence Skill - Sequence CRUD, scheduling, and safety controls.
"""

from __future__ import annotations
import logging
from datetime import datetime, timedelta
from typing import Optional
import uuid

logger = logging.getLogger(__name__)


class SequenceSkill:
    """
    Handles sequence operations:
    - Sequence CRUD
    - Step scheduling
    - Rate limiting
    - Sending window enforcement
    """

    def __init__(self, config: dict):
        self.config = config
        self.settings = config.get("agents", {}).get("KSEQ", {}).get("settings", {})
        self.max_daily_sends = self.settings.get("max_daily_sends", 200)
        self.sending_window_start = self.settings.get("sending_window_start", "08:00")
        self.sending_window_end = self.settings.get("sending_window_end", "18:00")
        self.warmup_enabled = self.settings.get("warmup_enabled", True)

    # -------------------------------------------------------------------------
    # Sequence CRUD
    # -------------------------------------------------------------------------

    def create_sequence(
        self,
        name: str,
        steps: list[dict] = None,
        sender_email: str = None,
        sender_name: str = None,
    ) -> dict:
        """
        Create a new sequence.

        Args:
            name: Sequence name
            steps: List of step definitions
            sender_email: Sender email address
            sender_name: Sender display name

        Returns:
            Created sequence dict
        """
        sequence_id = str(uuid.uuid4())

        # Process steps
        processed_steps = []
        for i, step in enumerate(steps or []):
            processed_steps.append({
                "id": str(uuid.uuid4()),
                "sequence_id": sequence_id,
                "step_number": i + 1,
                "channel": step.get("channel", "email"),
                "delay_days": step.get("delay_days", 0 if i == 0 else 3),
                "delay_hours": step.get("delay_hours", 0),
                "subject": step.get("subject", ""),
                "body": step.get("body", ""),
                "template_id": step.get("template_id"),
                "skip_if_replied": step.get("skip_if_replied", True),
                "skip_weekends": step.get("skip_weekends", True),
                "created_at": datetime.now().isoformat(),
            })

        sequence = {
            "id": sequence_id,
            "name": name,
            "status": "draft",
            "steps": processed_steps,
            "sender_email": sender_email,
            "sender_name": sender_name,
            "sending_window_start": self.sending_window_start,
            "sending_window_end": self.sending_window_end,
            "daily_limit": self.max_daily_sends,
            "total_enrolled": 0,
            "active_enrolled": 0,
            "created_at": datetime.now().isoformat(),
            "updated_at": datetime.now().isoformat(),
        }

        logger.info(f"[SequenceSkill] Created sequence: {name} ({sequence_id})")
        return sequence

    def add_step(
        self,
        sequence: dict,
        channel: str = "email",
        delay_days: int = 3,
        subject: str = "",
        body: str = "",
    ) -> dict:
        """Add a step to a sequence."""
        step_number = len(sequence.get("steps", [])) + 1

        step = {
            "id": str(uuid.uuid4()),
            "sequence_id": sequence["id"],
            "step_number": step_number,
            "channel": channel,
            "delay_days": delay_days,
            "delay_hours": 0,
            "subject": subject,
            "body": body,
            "skip_if_replied": True,
            "skip_weekends": True,
            "created_at": datetime.now().isoformat(),
        }

        sequence.setdefault("steps", []).append(step)
        sequence["updated_at"] = datetime.now().isoformat()

        return step

    # -------------------------------------------------------------------------
    # Enrollment
    # -------------------------------------------------------------------------

    def enroll_contact(
        self,
        sequence_id: str,
        contact_id: str,
        start_immediately: bool = False,
    ) -> dict:
        """
        Enroll a contact in a sequence.

        Returns enrollment record.
        """
        enrollment_id = str(uuid.uuid4())
        now = datetime.now()

        # Calculate first send time
        if start_immediately:
            first_send = self._get_next_send_time(now)
        else:
            first_send = self._get_next_send_time(now + timedelta(hours=1))

        enrollment = {
            "id": enrollment_id,
            "sequence_id": sequence_id,
            "contact_id": contact_id,
            "status": "active",  # active | paused | completed | bounced | replied
            "current_step": 1,
            "enrolled_at": now.isoformat(),
            "next_send_at": first_send.isoformat(),
            "last_sent_at": None,
            "completed_at": None,
        }

        logger.info(f"[SequenceSkill] Enrolled contact {contact_id} in sequence {sequence_id}")
        return enrollment

    def get_due_sends(
        self,
        enrollments: list[dict],
        sequences: dict[str, dict],
        limit: int = 50,
    ) -> list[dict]:
        """
        Get enrollments that are due to send.

        Returns list of {enrollment, sequence, step} dicts.
        """
        now = datetime.now()
        due = []

        for enrollment in enrollments:
            if enrollment["status"] != "active":
                continue

            next_send = enrollment.get("next_send_at")
            if not next_send:
                continue

            next_send_dt = datetime.fromisoformat(next_send)
            if next_send_dt > now:
                continue

            # Get sequence and step
            sequence = sequences.get(enrollment["sequence_id"])
            if not sequence or sequence.get("status") != "active":
                continue

            step = None
            for s in sequence.get("steps", []):
                if s["step_number"] == enrollment["current_step"]:
                    step = s
                    break

            if step:
                due.append({
                    "enrollment": enrollment,
                    "sequence": sequence,
                    "step": step,
                })

            if len(due) >= limit:
                break

        return due

    def advance_enrollment(self, enrollment: dict, sequence: dict) -> dict:
        """
        Advance enrollment to next step or mark complete.

        Returns updated enrollment.
        """
        current_step = enrollment["current_step"]
        total_steps = len(sequence.get("steps", []))

        if current_step >= total_steps:
            # Sequence complete
            enrollment["status"] = "completed"
            enrollment["completed_at"] = datetime.now().isoformat()
            enrollment["next_send_at"] = None
        else:
            # Advance to next step
            next_step = current_step + 1
            next_step_data = None

            for s in sequence.get("steps", []):
                if s["step_number"] == next_step:
                    next_step_data = s
                    break

            if next_step_data:
                delay_days = next_step_data.get("delay_days", 3)
                delay_hours = next_step_data.get("delay_hours", 0)
                next_send = datetime.now() + timedelta(days=delay_days, hours=delay_hours)

                enrollment["current_step"] = next_step
                enrollment["next_send_at"] = self._get_next_send_time(next_send).isoformat()

        enrollment["last_sent_at"] = datetime.now().isoformat()
        return enrollment

    # -------------------------------------------------------------------------
    # Rate Limiting & Safety
    # -------------------------------------------------------------------------

    def check_rate_limits(self, sender_email: str, sent_today: int) -> dict:
        """
        Check if sending is allowed based on rate limits.

        Returns:
            {"allowed": bool, "reason": str, "remaining": int}
        """
        daily_limit = self.max_daily_sends

        # Check warmup schedule
        if self.warmup_enabled:
            warmup_config = self.config.get("safety", {}).get("warmup_schedule", {})
            # Would check domain age and apply warmup limits
            # For now, use configured limit

        if sent_today >= daily_limit:
            return {
                "allowed": False,
                "reason": f"Daily limit reached ({daily_limit})",
                "remaining": 0,
            }

        # Check hourly limit
        hourly_limit = self.config.get("safety", {}).get("max_sends_per_hour", 50)
        # Would track hourly sends too

        return {
            "allowed": True,
            "reason": "ok",
            "remaining": daily_limit - sent_today,
        }

    def is_in_sending_window(self, dt: datetime = None) -> bool:
        """Check if current time is within sending window."""
        dt = dt or datetime.now()
        current_time = dt.strftime("%H:%M")

        return self.sending_window_start <= current_time <= self.sending_window_end

    def _get_next_send_time(self, dt: datetime) -> datetime:
        """
        Get next valid send time, respecting sending windows and weekends.
        """
        # Parse window times
        start_hour, start_min = map(int, self.sending_window_start.split(":"))
        end_hour, end_min = map(int, self.sending_window_end.split(":"))

        result = dt

        # Skip weekends
        while result.weekday() >= 5:  # Saturday = 5, Sunday = 6
            result += timedelta(days=1)
            result = result.replace(hour=start_hour, minute=start_min, second=0)

        # Check if within window
        current_time = result.hour * 60 + result.minute
        window_start = start_hour * 60 + start_min
        window_end = end_hour * 60 + end_min

        if current_time < window_start:
            # Before window - set to window start
            result = result.replace(hour=start_hour, minute=start_min, second=0)
        elif current_time > window_end:
            # After window - move to next day's window start
            result += timedelta(days=1)
            result = result.replace(hour=start_hour, minute=start_min, second=0)
            # Check for weekend again
            while result.weekday() >= 5:
                result += timedelta(days=1)

        return result

    # -------------------------------------------------------------------------
    # Pause/Resume
    # -------------------------------------------------------------------------

    def pause_enrollment(self, enrollment: dict, reason: str = None) -> dict:
        """Pause a contact's enrollment."""
        enrollment["status"] = "paused"
        enrollment["paused_at"] = datetime.now().isoformat()
        enrollment["pause_reason"] = reason
        return enrollment

    def resume_enrollment(self, enrollment: dict) -> dict:
        """Resume a paused enrollment."""
        if enrollment["status"] != "paused":
            return enrollment

        enrollment["status"] = "active"
        enrollment["paused_at"] = None
        enrollment["pause_reason"] = None

        # Recalculate next send time
        enrollment["next_send_at"] = self._get_next_send_time(datetime.now()).isoformat()
        return enrollment

    def mark_replied(self, enrollment: dict) -> dict:
        """Mark enrollment as replied (stops sequence)."""
        enrollment["status"] = "replied"
        enrollment["replied_at"] = datetime.now().isoformat()
        enrollment["next_send_at"] = None
        return enrollment

    def mark_bounced(self, enrollment: dict) -> dict:
        """Mark enrollment as bounced (stops sequence)."""
        enrollment["status"] = "bounced"
        enrollment["bounced_at"] = datetime.now().isoformat()
        enrollment["next_send_at"] = None
        return enrollment
