"""Rules execution engine for automating message handling."""

import re
from typing import Any

from django.db import transaction

from messages.models import Message, Tag, Thread
from rules.models import Rule


class RulesEngine:
    """
    Engine for evaluating and executing automation rules against messages and threads.
    """

    # Supported predicate operators
    OPERATORS = {
        "equals": lambda value, target: str(value).lower() == str(target).lower(),
        "contains": lambda value, target: str(target).lower() in str(value).lower(),
        "starts_with": lambda value, target: str(value).lower().startswith(str(target).lower()),
        "ends_with": lambda value, target: str(value).lower().endswith(str(target).lower()),
        "matches": lambda value, target: bool(re.search(target, str(value), re.IGNORECASE)),
        "greater_than": lambda value, target: float(value) > float(target),
        "less_than": lambda value, target: float(value) < float(target),
        "in": lambda value, target: str(value).lower() in [str(t).lower() for t in target],
        "not_in": lambda value, target: str(value).lower() not in [str(t).lower() for t in target],
    }

    @classmethod
    def evaluate_rules_for_message(cls, message: Message) -> list[dict]:
        """
        Evaluate all applicable rules for a message and execute matching actions.

        Returns a list of executed actions.
        """
        thread = message.thread
        workspace_id = thread.workspace_id

        # Get all enabled rules for this workspace, ordered by priority
        rules = Rule.objects.filter(
            workspace_id=workspace_id,
            is_enabled=True,
        ).order_by("-priority", "id")

        executed_actions = []

        for rule in rules:
            if cls._evaluate_predicate(rule.predicate, message, thread):
                result = cls._execute_action(rule, message, thread)
                executed_actions.append(
                    {
                        "rule_id": rule.id,
                        "rule_name": rule.name,
                        "action": rule.action,
                        "result": result,
                    }
                )

        return executed_actions

    @classmethod
    def evaluate_rules_for_thread(cls, thread: Thread) -> list[dict]:
        """
        Evaluate all applicable rules for a thread and execute matching actions.

        Returns a list of executed actions.
        """
        workspace_id = thread.workspace_id

        rules = Rule.objects.filter(
            workspace_id=workspace_id,
            is_enabled=True,
        ).order_by("-priority", "id")

        executed_actions = []

        for rule in rules:
            if cls._evaluate_thread_predicate(rule.predicate, thread):
                result = cls._execute_thread_action(rule, thread)
                executed_actions.append(
                    {
                        "rule_id": rule.id,
                        "rule_name": rule.name,
                        "action": rule.action,
                        "result": result,
                    }
                )

        return executed_actions

    @classmethod
    def _evaluate_predicate(cls, predicate: dict, message: Message, thread: Thread) -> bool:
        """
        Evaluate a predicate against a message and its thread.

        Predicate format:
        {
            "conditions": [
                {"field": "content", "operator": "contains", "value": "bug"},
                {"field": "thread.status", "operator": "equals", "value": "open"}
            ],
            "match": "all" | "any"  # default: "all"
        }
        """
        if not predicate:
            return False

        conditions = predicate.get("conditions", [])
        if not conditions:
            # Simple predicate format for backward compatibility
            conditions = [predicate] if predicate else []

        match_type = predicate.get("match", "all")

        results = []
        for condition in conditions:
            result = cls._evaluate_condition(condition, message, thread)
            results.append(result)

        if match_type == "any":
            return any(results)
        return all(results)

    @classmethod
    def _evaluate_thread_predicate(cls, predicate: dict, thread: Thread) -> bool:
        """Evaluate a predicate against a thread only (no specific message)."""
        if not predicate:
            return False

        conditions = predicate.get("conditions", [])
        if not conditions:
            conditions = [predicate] if predicate else []

        match_type = predicate.get("match", "all")

        results = []
        for condition in conditions:
            result = cls._evaluate_thread_condition(condition, thread)
            results.append(result)

        if match_type == "any":
            return any(results)
        return all(results)

    @classmethod
    def _evaluate_condition(cls, condition: dict, message: Message, thread: Thread) -> bool:
        """Evaluate a single condition."""
        field = condition.get("field", "")
        operator = condition.get("operator", "equals")
        target = condition.get("value", "")

        # Get the actual value based on field
        value = cls._get_field_value(field, message, thread)

        if value is None:
            return False

        # Apply operator
        op_func = cls.OPERATORS.get(operator, cls.OPERATORS["equals"])
        try:
            return op_func(value, target)
        except (ValueError, TypeError):
            return False

    @classmethod
    def _evaluate_thread_condition(cls, condition: dict, thread: Thread) -> bool:
        """Evaluate a single condition against a thread."""
        field = condition.get("field", "")
        operator = condition.get("operator", "equals")
        target = condition.get("value", "")

        # Get thread field value
        value = cls._get_thread_field_value(field, thread)

        if value is None:
            return False

        op_func = cls.OPERATORS.get(operator, cls.OPERATORS["equals"])
        try:
            return op_func(value, target)
        except (ValueError, TypeError):
            return False

    @classmethod
    def _get_field_value(cls, field: str, message: Message, thread: Thread) -> Any:
        """Get the value of a field from a message or thread."""
        if field.startswith("thread."):
            thread_field = field[7:]  # Remove "thread." prefix
            return cls._get_thread_field_value(thread_field, thread)

        # Message fields
        field_map = {
            "content": message.content,
            "external_id": message.external_id,
            "created_at": message.created_at,
        }

        return field_map.get(field)

    @classmethod
    def _get_thread_field_value(cls, field: str, thread: Thread) -> Any:
        """Get the value of a field from a thread."""
        field_map = {
            "status": thread.status,
            "title": thread.title,
            "external_id": thread.external_id,
            "created_at": thread.created_at,
            "updated_at": thread.updated_at,
        }

        if field == "tags":
            return [tag.name for tag in thread.tags.all()]

        return field_map.get(field)

    @classmethod
    def _execute_action(cls, rule: Rule, message: Message | None, thread: Thread) -> dict:
        """Execute an action for a matched rule."""
        action = rule.action
        params = rule.action_params or {}

        action_handlers = {
            "tag": cls._action_add_tag,
            "close": cls._action_close_thread,
            "snooze": cls._action_snooze_thread,
            "assign": cls._action_assign,
            "notify": cls._action_notify,
        }

        handler = action_handlers.get(action)
        if handler:
            return handler(thread, params)

        return {"status": "unknown_action", "action": action}

    @classmethod
    def _execute_thread_action(cls, rule: Rule, thread: Thread) -> dict:
        """Execute an action for a matched rule on a thread."""
        return cls._execute_action(rule, None, thread)

    @classmethod
    @transaction.atomic
    def _action_add_tag(cls, thread: Thread, params: dict) -> dict:
        """Add a tag to the thread."""
        tag_id = params.get("tag_id")
        tag_name = params.get("tag_name")

        if tag_id:
            try:
                tag = Tag.objects.get(id=tag_id, workspace_id=thread.workspace_id)
            except Tag.DoesNotExist:
                return {"status": "error", "message": f"Tag {tag_id} not found"}
        elif tag_name:
            tag, _ = Tag.objects.get_or_create(
                workspace_id=thread.workspace_id,
                name=tag_name,
                defaults={"color": params.get("tag_color", "#7c3aed")},
            )
        else:
            return {"status": "error", "message": "No tag_id or tag_name provided"}

        thread.tags.add(tag)
        return {"status": "success", "tag_added": tag.name}

    @classmethod
    @transaction.atomic
    def _action_close_thread(cls, thread: Thread, params: dict) -> dict:
        """Close the thread."""
        thread.status = "closed"
        thread.save(update_fields=["status", "updated_at"])
        return {"status": "success", "new_status": "closed"}

    @classmethod
    @transaction.atomic
    def _action_snooze_thread(cls, thread: Thread, params: dict) -> dict:
        """Snooze the thread."""
        thread.status = "snoozed"
        thread.save(update_fields=["status", "updated_at"])
        return {"status": "success", "new_status": "snoozed"}

    @classmethod
    def _action_assign(cls, thread: Thread, params: dict) -> dict:
        """Assign the thread to a user (placeholder for future implementation)."""
        assignee_id = params.get("assignee_id")
        return {"status": "pending", "message": f"Assignment to {assignee_id} requires implementation"}

    @classmethod
    def _action_notify(cls, thread: Thread, params: dict) -> dict:
        """Send a notification (placeholder for future implementation)."""
        channel = params.get("channel", "email")
        recipient = params.get("recipient")
        return {"status": "pending", "message": f"Notification via {channel} to {recipient} requires implementation"}


def process_new_message(message: Message) -> list[dict]:
    """Convenience function to process a new message through the rules engine."""
    return RulesEngine.evaluate_rules_for_message(message)


def process_thread_update(thread: Thread) -> list[dict]:
    """Convenience function to process a thread update through the rules engine."""
    return RulesEngine.evaluate_rules_for_thread(thread)
