#!/usr/bin/env python3
"""
Evictor configuration tool for CAST.AI External MCP Server.

This is the FIRST WRITE OPERATION in the external MCP server.
It allows updating evictor configuration with strict safety mechanisms.
"""

import json
import re
from typing import Any

from fastmcp import FastMCP

from ..cache import resolve_cluster_id
from ..client import make_request
from ..logger import logger


def validate_cycle_interval(interval: str) -> tuple[bool, str]:
    """
    Validate Go duration format for cycle interval.

    Valid formats: "30s", "5m", "1h30m", "2h", etc.

    Returns:
        Tuple of (is_valid, error_message)
    """
    pattern = r"^(\d+h)?(\d+m)?(\d+s)?(\d+ms)?(\d+us)?(\d+ns)?$"
    if not re.match(pattern, interval):
        return False, (
            f"Invalid cycle interval format: '{interval}'. "
            "Must be a Go duration (e.g., '30s', '5m', '1h30m')"
        )

    # Warn if interval is very short
    if interval.endswith("s"):
        try:
            seconds = int(interval[:-1])
            if seconds < 30:
                return True, f"WARNING: Very short cycle interval ({interval}) may cause excessive API load"
        except ValueError:
            pass

    return True, ""


def assess_risk_level(current_evictor: dict[str, Any], updated_evictor: dict[str, Any]) -> dict[str, Any]:
    """
    Assess the risk level of proposed evictor changes.

    Returns:
        Dictionary with risk assessment details
    """
    risks = []

    # Check for enabling aggressive mode
    if not current_evictor.get("aggressiveMode") and updated_evictor.get("aggressiveMode"):
        if not updated_evictor.get("dryRun"):
            risks.append(
                {
                    "level": "CRITICAL",
                    "change": "Enabling aggressive mode with dry_run=false",
                    "impact": "May interrupt single-replica applications and Jobs",
                }
            )
        else:
            risks.append(
                {
                    "level": "MEDIUM",
                    "change": "Enabling aggressive mode (dry-run)",
                    "impact": "Will test eviction of single-replica workloads",
                }
            )

    # Check for disabling dry-run
    if current_evictor.get("dryRun") and not updated_evictor.get("dryRun"):
        if updated_evictor.get("aggressiveMode"):
            risks.append(
                {
                    "level": "CRITICAL",
                    "change": "Disabling dry-run with aggressive mode enabled",
                    "impact": "Will perform actual aggressive evictions - may cause disruptions",
                }
            )
        else:
            risks.append(
                {
                    "level": "MEDIUM",
                    "change": "Disabling dry-run",
                    "impact": "Will perform actual pod evictions",
                }
            )

    # Check for enabling evictor
    if not current_evictor.get("enabled") and updated_evictor.get("enabled"):
        if not updated_evictor.get("dryRun"):
            risks.append(
                {
                    "level": "HIGH",
                    "change": "Enabling evictor with dry_run=false",
                    "impact": "Will start actual pod evictions immediately",
                }
            )
        else:
            risks.append(
                {
                    "level": "LOW",
                    "change": "Enabling evictor (dry-run mode)",
                    "impact": "Will simulate evictions without executing",
                }
            )

    # Determine overall risk level
    risk_levels = [r["level"] for r in risks]
    if "CRITICAL" in risk_levels:
        overall_risk = "CRITICAL"
    elif "HIGH" in risk_levels:
        overall_risk = "HIGH"
    elif "MEDIUM" in risk_levels:
        overall_risk = "MEDIUM"
    else:
        overall_risk = "LOW"

    return {"overall_risk": overall_risk, "risks": risks, "requires_extra_caution": overall_risk in ["CRITICAL", "HIGH"]}


def register_evictor_tool(mcp: FastMCP):
    """Register the evictor configuration tool."""

    @mcp.tool()
    async def update_cluster_evictor(
        cluster_id_or_name: str,
        enabled: bool | None = None,
        dry_run: bool | None = None,
        aggressive_mode: bool | None = None,
        scoped_mode: bool | None = None,
        cycle_interval: str | None = None,
        node_grace_period_minutes: int | None = None,
        confirm_impact: bool = False,
    ) -> str:
        """
        Update evictor configuration for a cluster policy.

        ⚠️  CRITICAL WARNING: This tool modifies cluster autoscaling behavior.
        The evictor controls pod eviction and rescheduling. Misconfiguration can
        cause production workload interruptions.

        SAFETY REQUIREMENTS:
        1. Always test with dry_run=true first
        2. Never enable aggressive_mode in production without testing
        3. Ensure PodDisruptionBudgets are configured for critical workloads
        4. Set confirm_impact=true to acknowledge risks

        EVICTOR PARAMETERS:
        - enabled: Enable/disable the evictor (default: false)
        - dry_run: If true, simulate evictions without executing (default: true, RECOMMENDED)
        - aggressive_mode: Evict single-replica workloads and Jobs (default: false, USE WITH CAUTION)
        - scoped_mode: Limit to CAST.AI-managed nodes only (default: false)
        - cycle_interval: Time between eviction cycles (e.g., "5m", "30s", default: "5m")
        - node_grace_period_minutes: Grace period before evicting new nodes (default: 5, range: 1-60)

        PROTECTION MECHANISMS:
        - PodDisruptionBudgets are always respected
        - DaemonSet pods are automatically skipped
        - Pods with autoscaling.cast.ai/removal-disabled=true are protected
        - Pods with cluster-autoscaler.kubernetes.io/safe-to-evict=false are protected

        RECOMMENDED WORKFLOW:
        1. Enable with dry_run=true to test
        2. Monitor logs and verify expected behavior
        3. Set dry_run=false only after validation
        4. Monitor cluster after changes

        ROLLBACK:
        If issues occur, immediately set:
        - enabled=false (stops all evictions)
        - Or dry_run=true (switches to simulation mode)

        Args:
            cluster_id_or_name: Cluster ID (UUID) or cluster name
            enabled: Enable/disable evictor
            dry_run: Enable/disable dry-run mode
            aggressive_mode: Enable/disable aggressive eviction mode
            scoped_mode: Enable/disable scoped mode (CAST.AI nodes only)
            cycle_interval: Eviction cycle interval (Go duration format)
            node_grace_period_minutes: Node grace period in minutes (1-60)
            confirm_impact: REQUIRED - Set to true to confirm you understand the impact

        Returns:
            JSON string with update result including before/after comparison
        """
        try:
            # Step 1: Validate cluster exists and get cluster ID
            cluster_id = await resolve_cluster_id(cluster_id_or_name)
            logger.info("Starting evictor update", cluster_id=cluster_id, confirmed=confirm_impact)

            # Step 2: Validate inputs
            errors = []

            if cycle_interval is not None:
                is_valid, error_msg = validate_cycle_interval(cycle_interval)
                if not is_valid:
                    errors.append(error_msg)
                elif error_msg:  # Warning message
                    logger.warning("Cycle interval warning", message=error_msg)

            if node_grace_period_minutes is not None:
                if node_grace_period_minutes < 1 or node_grace_period_minutes > 60:
                    errors.append(
                        f"node_grace_period_minutes must be between 1 and 60, got: {node_grace_period_minutes}"
                    )

            if errors:
                return json.dumps({"error": "Validation failed", "validation_errors": errors})

            # Step 3: Read current policy
            logger.info("Reading current policy for cluster", cluster_id=cluster_id)
            try:
                current_policy = await make_request("GET", f"/v1/kubernetes/clusters/{cluster_id}/policies")
            except Exception as e:
                return json.dumps(
                    {
                        "error": "Failed to read current policy",
                        "details": str(e),
                        "suggestion": "Verify cluster name/ID is correct using list_clusters",
                    }
                )

            # Step 4: Extract and update evictor config
            # Evictor is nested under nodeDownscaler
            node_downscaler = current_policy.get("nodeDownscaler", {})
            current_evictor = node_downscaler.get("evictor", {})
            updated_evictor = current_evictor.copy()

            # Track what's being changed
            changes = {}

            if enabled is not None:
                updated_evictor["enabled"] = enabled
                changes["enabled"] = {"from": current_evictor.get("enabled"), "to": enabled}

            if dry_run is not None:
                updated_evictor["dryRun"] = dry_run
                changes["dryRun"] = {"from": current_evictor.get("dryRun"), "to": dry_run}

            if aggressive_mode is not None:
                updated_evictor["aggressiveMode"] = aggressive_mode
                changes["aggressiveMode"] = {"from": current_evictor.get("aggressiveMode"), "to": aggressive_mode}

            if scoped_mode is not None:
                updated_evictor["scopedMode"] = scoped_mode
                changes["scopedMode"] = {"from": current_evictor.get("scopedMode"), "to": scoped_mode}

            if cycle_interval is not None:
                updated_evictor["cycleInterval"] = cycle_interval
                changes["cycleInterval"] = {"from": current_evictor.get("cycleInterval"), "to": cycle_interval}

            if node_grace_period_minutes is not None:
                updated_evictor["nodeGracePeriodMinutes"] = node_grace_period_minutes
                changes["nodeGracePeriodMinutes"] = {
                    "from": current_evictor.get("nodeGracePeriodMinutes"),
                    "to": node_grace_period_minutes,
                }

            # Check if anything is actually changing
            if not changes:
                return json.dumps(
                    {
                        "error": "No changes specified",
                        "suggestion": "Provide at least one parameter to update",
                        "current_config": current_evictor,
                    }
                )

            # Step 5: Assess risk level
            risk_assessment = assess_risk_level(current_evictor, updated_evictor)

            # Step 6: Check confirmation requirement
            if not confirm_impact:
                return json.dumps(
                    {
                        "error": "Confirmation required",
                        "message": (
                            "⚠️  SAFETY CHECK: You must set confirm_impact=true to proceed.\n\n"
                            "This operation will modify cluster autoscaling behavior. "
                            "Evictor controls pod eviction and rescheduling - misconfiguration "
                            "can cause production workload interruptions."
                        ),
                        "proposed_changes": changes,
                        "risk_assessment": risk_assessment,
                        "current_config": current_evictor,
                        "updated_config": updated_evictor,
                        "required_action": "Set confirm_impact=true to acknowledge and proceed",
                    }
                )

            # Additional warning for critical risk changes
            if risk_assessment["requires_extra_caution"]:
                logger.warning(
                    "High-risk evictor update confirmed",
                    cluster_id=cluster_id,
                    risk_level=risk_assessment["overall_risk"],
                )

            # Step 7: Build complete policy body (preserve other settings)
            updated_policy = current_policy.copy()
            # Update the nested evictor under nodeDownscaler
            if "nodeDownscaler" not in updated_policy:
                updated_policy["nodeDownscaler"] = {}
            updated_policy["nodeDownscaler"]["evictor"] = updated_evictor

            # Step 8: Write back to API
            logger.info("Updating policy for cluster", cluster_id=cluster_id, changes=len(changes))
            try:
                result = await make_request("PUT", f"/v1/kubernetes/clusters/{cluster_id}/policies", body=updated_policy)
            except Exception as e:
                logger.error("Policy update failed", error=str(e))
                return json.dumps(
                    {
                        "error": "Failed to update policy",
                        "details": str(e),
                        "rollback_suggestion": "No changes were applied. Your previous configuration is still active.",
                    }
                )

            # Step 9: Verify update
            logger.info("Verifying update for cluster", cluster_id=cluster_id)
            try:
                verified_policy = await make_request("GET", f"/v1/kubernetes/clusters/{cluster_id}/policies")
                verified_node_downscaler = verified_policy.get("nodeDownscaler", {})
                verified_evictor = verified_node_downscaler.get("evictor", {})
            except Exception as e:
                logger.warning("Could not verify update", error=str(e))
                verified_evictor = None

            # Step 10: Return success response
            logger.info("Evictor update completed successfully", cluster_id=cluster_id, changes=len(changes))

            response = {
                "status": "success",
                "message": "Evictor configuration updated successfully",
                "cluster_id": cluster_id,
                "changes_applied": changes,
                "risk_assessment": risk_assessment,
                "before": current_evictor,
                "after": verified_evictor if verified_evictor else updated_evictor,
                "timestamp": result.get("updatedAt"),
            }

            # Add monitoring recommendations based on risk level
            if risk_assessment["overall_risk"] in ["CRITICAL", "HIGH"]:
                response["monitoring_recommendation"] = (
                    "⚠️  Monitor your cluster closely for the next 15-30 minutes. "
                    "Watch for unexpected pod evictions, application restarts, or service disruptions. "
                    "If issues occur, immediately run this tool with enabled=false or dry_run=true."
                )

            # Add rollback instructions
            response["rollback"] = {
                "stop_evictions": "Set enabled=false",
                "switch_to_dry_run": "Set dry_run=true",
                "restore_previous": f"Use these values: {json.dumps(current_evictor)}",
            }

            return json.dumps(response, indent=2)

        except Exception as e:
            logger.error("Evictor update error", error=str(e), cluster_id_or_name=cluster_id_or_name)
            return json.dumps(
                {
                    "error": str(e),
                    "troubleshooting": {
                        "check_cluster": "Verify cluster name/ID using list_clusters",
                        "check_permissions": "Ensure API key has write permissions",
                        "check_api_status": "Verify CAST.AI API is accessible",
                    },
                }
            )