"""
KSEQ - KiessSequencer Agent
Handles sequence orchestration, scheduling, and sending.
"""

from __future__ import annotations
import logging
from datetime import datetime
from typing import Optional

from ..core.agent import BaseAgent
from ..core.memory import MemoryEngine
from ..skills.sequence import SequenceSkill
from ..skills.email import EmailSkill
from ..models.sequence import Sequence, SequenceStep
from ..models.message import SentMessage

logger = logging.getLogger(__name__)


class KiessSequencerAgent(BaseAgent):
    """
    Sequencer agent responsible for:
    - Creating and managing sequences
    - Enrolling contacts
    - Scheduling and sending emails
    - Rate limiting and safety controls
    """

    codename = "KSEQ"
    name = "KiessSequencer"
    description = "Sequence orchestration, scheduling, safety"

    def __init__(self, config: dict, memory: MemoryEngine):
        super().__init__(config, memory)
        self.sequence_skill = SequenceSkill(config)
        self.email_skill = EmailSkill(config)

    def run(self, task: str, **kwargs) -> str:
        """
        Execute a sequencer task.

        Supported tasks:
        - create: Create a new sequence
        - enroll: Enroll contacts in a sequence
        - send: Process and send due emails
        - pause: Pause a sequence or enrollment
        - status: Get sequence status
        """
        is_dry_send = (task == "send" or "process" in task.lower()) and bool(kwargs.get("dry_run"))
        if not is_dry_send:
            self.update_status("running", task)

        try:
            if task == "create" or "create" in task.lower():
                return self._handle_create(**kwargs)
            elif task == "add_step" or "add step" in task.lower():
                return self._handle_add_step(**kwargs)
            elif task == "enroll" or "enroll" in task.lower():
                return self._handle_enroll(**kwargs)
            elif task == "send" or "process" in task.lower():
                return self._handle_send(**kwargs)
            elif task == "pause" in task.lower():
                return self._handle_pause(**kwargs)
            elif task == "status" or "list" in task.lower():
                return self._handle_status(**kwargs)
            else:
                return self._handle_natural_language(task, **kwargs)

        except Exception as e:
            logger.error(f"[KSEQ] Error: {e}")
            if not is_dry_send:
                self.update_status("error", task)
            return f"Error: {str(e)}"

        finally:
            if not is_dry_send:
                self.update_status("idle")

    def _handle_create(
        self,
        name: str = None,
        steps: list[dict] = None,
        sender_email: str = None,
        **kwargs
    ) -> str:
        """Create a new sequence."""
        if not name:
            return "Error: name is required to create a sequence"

        # Create sequence using skill
        sequence = self.sequence_skill.create_sequence(
            name=name,
            steps=steps or [],
            sender_email=sender_email,
        )

        # Save to data
        sequences = self.memory.load_data("sequences")
        sequences.append(sequence)
        self.memory.save_data("sequences", sequences)

        # Log action
        self.memory.log(
            "kseq",
            f"**Created Sequence**\n"
            f"- Name: {name}\n"
            f"- ID: {sequence['id']}\n"
            f"- Steps: {len(sequence.get('steps', []))}"
        )

        return f"Sequence '{name}' created with {len(sequence.get('steps', []))} steps. ID: {sequence['id']}"

    def _handle_add_step(
        self,
        sequence_id: str = None,
        channel: str = "email",
        delay_days: int = 3,
        subject: str = "",
        body: str = "",
        **kwargs,
    ) -> str:
        """Add a step to an existing sequence."""
        if not sequence_id:
            return "Error: sequence_id is required"

        sequences = self.memory.load_data("sequences")
        sequence = next((item for item in sequences if item["id"] == sequence_id), None)
        if not sequence:
            return f"Error: Sequence {sequence_id} not found"

        step = self.sequence_skill.add_step(
            sequence=sequence,
            channel=channel,
            delay_days=max(0, int(delay_days)),
            subject=subject,
            body=body,
        )
        self.memory.save_data("sequences", sequences)

        return (
            f"Added step #{step['step_number']} to '{sequence['name']}' "
            f"(channel={step['channel']}, delay_days={step['delay_days']})"
        )

    def _handle_enroll(
        self,
        sequence_id: str = None,
        contact_ids: list[str] = None,
        segment_id: str = None,
        start_immediately: bool = False,
        **kwargs
    ) -> str:
        """Enroll contacts in a sequence."""
        if not sequence_id:
            return "Error: sequence_id is required"

        # Load sequence
        sequences = self.memory.load_data("sequences")
        sequence = None
        for s in sequences:
            if s["id"] == sequence_id:
                sequence = s
                break

        if not sequence:
            return f"Error: Sequence {sequence_id} not found"

        if sequence.get("status") != "active":
            return f"Error: Sequence is not active (status: {sequence.get('status')})"

        # Get contacts to enroll
        contacts_to_enroll = []
        if contact_ids:
            contacts_to_enroll = contact_ids
        elif segment_id:
            # Would load segment and get matching contacts
            contacts = self.memory.load_data("contacts")
            contacts_to_enroll = [c["id"] for c in contacts if c.get("status") == "new"]

        if not contacts_to_enroll:
            return "No contacts to enroll"

        # Load existing enrollments
        enrollments = self.memory.load_data("enrollments")
        enrolled_contacts = {e["contact_id"] for e in enrollments if e["sequence_id"] == sequence_id}

        # Enroll new contacts
        new_enrollments = []
        for contact_id in contacts_to_enroll:
            if contact_id in enrolled_contacts:
                continue

            enrollment = self.sequence_skill.enroll_contact(
                sequence_id=sequence_id,
                contact_id=contact_id,
                start_immediately=start_immediately,
            )
            new_enrollments.append(enrollment)
            enrollments.append(enrollment)

        # Save enrollments
        self.memory.save_data("enrollments", enrollments)

        # Update sequence stats
        sequence["total_enrolled"] = sequence.get("total_enrolled", 0) + len(new_enrollments)
        sequence["active_enrolled"] = sequence.get("active_enrolled", 0) + len(new_enrollments)
        self.memory.save_data("sequences", sequences)

        self.memory.log(
            "kseq",
            f"**Enrolled Contacts**\n"
            f"- Sequence: {sequence['name']}\n"
            f"- New enrollments: {len(new_enrollments)}\n"
            f"- Total active: {sequence['active_enrolled']}"
        )

        return f"Enrolled {len(new_enrollments)} contacts in '{sequence['name']}'"

    def _handle_send(self, dry_run: bool = False, limit: int = 50, **kwargs) -> str:
        """Process and send due emails."""
        # Load data
        enrollments = self.memory.load_data("enrollments")
        sequences_list = self.memory.load_data("sequences")
        contacts = self.memory.load_data("contacts")
        accounts = self.memory.load_data("accounts")
        messages = self.memory.load_data("messages")

        # Build lookups
        sequences = {s["id"]: s for s in sequences_list}
        contacts_map = {c["id"]: c for c in contacts}
        accounts_map = {a["id"]: a for a in accounts}

        # Get due sends
        due_sends = self.sequence_skill.get_due_sends(enrollments, sequences, limit=limit)

        if not due_sends:
            return "No emails due to send"

        # Check rate limits
        today = datetime.now().date().isoformat()
        sent_today = sum(
            1 for m in messages
            if m.get("sent_at", "").startswith(today)
        )

        rate_check = self.sequence_skill.check_rate_limits("default", sent_today)
        if not rate_check["allowed"]:
            return f"Rate limit reached: {rate_check['reason']}"

        if dry_run:
            return self._simulate_send_run(
                due_sends=due_sends,
                contacts_map=contacts_map,
                accounts_map=accounts_map,
                rate_remaining=rate_check["remaining"],
            )

        # Process sends
        sent_count = 0
        errors = []
        total_attempted = 0

        for item in due_sends[:rate_check["remaining"]]:
            enrollment = item["enrollment"]
            sequence = item["sequence"]
            step = item["step"]
            contact = contacts_map.get(enrollment["contact_id"])
            account = accounts_map.get(contact.get("account_id")) if contact else None

            if not contact:
                continue

            if not contact.get("email"):
                continue

            # Check if contact can be contacted
            if contact.get("dnc") or contact.get("bounced") or contact.get("unsubscribed"):
                self.sequence_skill.pause_enrollment(enrollment, "Contact is DNC/bounced/unsubscribed")
                continue

            # Prepare email
            subject = step.get("subject", "")
            body = step.get("body", "")

            # Basic variable substitution
            subject = self._render_variables(subject, contact, sequence, account)
            body = self._render_variables(body, contact, sequence, account)

            # Send
            total_attempted += 1
            result = self.email_skill.send_email(
                to_email=contact["email"],
                from_email=sequence.get("sender_email", ""),
                from_name=sequence.get("sender_name"),
                subject=subject,
                body=body,
                html_body=self.email_skill.text_to_html(body),
                dry_run=dry_run,
            )

            if result["success"]:
                sent_count += 1

                # Record sent message
                sent_message = {
                    "id": result["message_id"],
                    "contact_id": contact["id"],
                    "sequence_id": sequence["id"],
                    "step_id": step["id"],
                    "to_email": contact["email"],
                    "from_email": sequence.get("sender_email", ""),
                    "subject": subject,
                    "body": body,
                    "sent_at": datetime.now().isoformat(),
                    "status": "sent",
                    "bounced": False,
                }
                messages.append(sent_message)

                # Advance enrollment
                self.sequence_skill.advance_enrollment(enrollment, sequence)

                # Update contact
                contact["last_contacted_at"] = datetime.now().isoformat()
                if contact.get("status") == "new":
                    contact["status"] = "contacted"

            else:
                errors.append(f"{contact['email']}: {result.get('error')}")

                # Handle bounces
                if result.get("bounce_type") == "hard":
                    self.sequence_skill.mark_bounced(enrollment)
                    contact["bounced"] = True

        # Save all data
        self.memory.save_data("enrollments", enrollments)
        self.memory.save_data("messages", messages)
        self.memory.save_data("contacts", contacts)

        # Log
        self.memory.log(
            "kseq",
            f"**Send Run Complete**\n"
            f"- Due: {len(due_sends)}\n"
            f"- Attempted: {total_attempted}\n"
            f"- Sent: {sent_count}\n"
            f"- Errors: {len(errors)}\n"
            f"- Dry run: {dry_run}"
        )

        result_msg = f"Sent {sent_count}/{len(due_sends)} emails"
        if dry_run:
            result_msg = f"[DRY RUN] Would send {sent_count} emails"
        if errors:
            result_msg += f"\nErrors: {errors[:3]}"

        return result_msg

    def _handle_pause(self, sequence_id: str = None, **kwargs) -> str:
        """Pause a sequence."""
        if not sequence_id:
            return "Error: sequence_id required"

        sequences = self.memory.load_data("sequences")
        for s in sequences:
            if s["id"] == sequence_id:
                s["status"] = "paused"
                s["paused_at"] = datetime.now().isoformat()
                self.memory.save_data("sequences", sequences)
                return f"Sequence '{s['name']}' paused"

        return f"Sequence {sequence_id} not found"

    def _handle_status(self, **kwargs) -> str:
        """Get sequence status."""
        sequences = self.memory.load_data("sequences")
        enrollments = self.memory.load_data("enrollments")

        if not sequences:
            return "No sequences found. Create one with `sequence create`"

        lines = ["**Sequences**\n"]
        for s in sequences:
            active = sum(1 for e in enrollments if e["sequence_id"] == s["id"] and e["status"] == "active")
            lines.append(
                f"- **{s['name']}** ({s['status']})\n"
                f"  Steps: {len(s.get('steps', []))} | Active: {active} | Total: {s.get('total_enrolled', 0)}"
            )

        return "\n".join(lines)

    def _handle_natural_language(self, task: str, **kwargs) -> str:
        """Handle natural language task requests."""
        sequences = self.memory.load_data("sequences")
        enrollments = self.memory.load_data("enrollments")

        context = (
            f"Sequences: {len(sequences)}\n"
            f"Active enrollments: {sum(1 for e in enrollments if e.get('status') == 'active')}\n"
        )

        response = self.think(
            user_message=task,
            context=context,
        )

        return response.content

    def _render_variables(
        self,
        text: str,
        contact: dict,
        sequence: dict,
        account: dict | None = None,
    ) -> str:
        """Simple variable substitution."""
        account = account or {}
        company_name = contact.get("company") or account.get("name") or ""
        replacements = {
            "{{first_name}}": contact.get("first_name", ""),
            "{{last_name}}": contact.get("last_name", ""),
            "{{email}}": contact.get("email", ""),
            "{{title}}": contact.get("title", ""),
            "{{company}}": company_name,
        }

        for var, value in replacements.items():
            text = text.replace(var, value or "")

        return text

    def _simulate_send_run(
        self,
        due_sends: list[dict],
        contacts_map: dict[str, dict],
        accounts_map: dict[str, dict],
        rate_remaining: int,
    ) -> str:
        """Simulate a send run without mutating files or in-memory records."""
        simulated = 0
        skipped = 0
        preview: list[str] = []

        for item in due_sends[:rate_remaining]:
            enrollment = item["enrollment"]
            sequence = item["sequence"]
            step = item["step"]
            contact = contacts_map.get(enrollment["contact_id"])
            account = accounts_map.get(contact.get("account_id")) if contact else None

            if not contact or not contact.get("email"):
                skipped += 1
                continue

            if contact.get("dnc") or contact.get("bounced") or contact.get("unsubscribed"):
                skipped += 1
                continue

            subject = self._render_variables(step.get("subject", ""), contact, sequence, account)
            body = self._render_variables(step.get("body", ""), contact, sequence, account)
            simulated += 1

            if len(preview) < 3:
                preview.append(
                    f"- {contact['email']} | subject='{subject[:60]}' | body_chars={len(body)}"
                )

        output = (
            f"[DRY RUN] Would send {simulated}/{min(len(due_sends), rate_remaining)} due emails\n"
            f"Skipped (invalid/DNC): {skipped}\n"
            "No state was modified."
        )
        if preview:
            output += "\nPreview:\n" + "\n".join(preview)
        return output
