"""
Resource-level pattern assignment with jitter algorithms.

Extracted from existing pipeline generate package. Implements deterministic but
varied patterns across different resources to create organic-feeling variance
while maintaining reproducibility. Each resource gets unique jitter patterns
based on its characteristics.

This creates the 0.8x-1.2x intensity range and 0-1hr phase shift variations
mentioned in the variance system requirements.
"""

import hashlib
from typing import Dict, List, Any

import numpy as np


def hash_resource_jitter_seed(resource_id: str, jitter_type: str = "base") -> int:
    """
    Create deterministic seed from resource ID for reproducible jitter patterns.

    Args:
        resource_id: Unique resource identifier
        jitter_type: Type of jitter (base, intensity, phase, timing)

    Returns:
        Deterministic integer seed for this resource's jitter
    """
    combined = f"{jitter_type}_{resource_id}"
    hash_value = hashlib.md5(combined.encode()).hexdigest()
    return int(hash_value[:8], 16)


def calculate_resource_intensity_jitter(
    resource_id: str, base_intensity: float = 1.0, jitter_range: float = 0.2
) -> float:
    """
    Calculate resource-specific intensity jitter (0.8x-1.2x range).

    Args:
        resource_id: Unique resource identifier
        base_intensity: Base intensity multiplier
        jitter_range: Jitter range around base (0.2 = ±20%)

    Returns:
        Jittered intensity multiplier for this resource
    """
    np.random.seed(hash_resource_jitter_seed(resource_id, "intensity"))

    # Generate jitter in the range [base_intensity - jitter_range, base_intensity + jitter_range]
    min_intensity = base_intensity - jitter_range
    max_intensity = base_intensity + jitter_range

    # Ensure we don't go below 0.1x (10%) or above 3.0x (300%)
    min_intensity = max(min_intensity, 0.1)
    max_intensity = min(max_intensity, 3.0)

    return np.random.uniform(min_intensity, max_intensity)


def calculate_resource_phase_jitter(
    resource_id: str, max_phase_hours: float = 1.0
) -> float:
    """
    Calculate resource-specific phase jitter (0-1hr phase shift).

    Args:
        resource_id: Unique resource identifier
        max_phase_hours: Maximum phase shift in hours

    Returns:
        Phase shift in hours for this resource
    """
    np.random.seed(hash_resource_jitter_seed(resource_id, "phase"))

    # Generate phase shift between 0 and max_phase_hours
    return np.random.uniform(0, max_phase_hours)


def apply_resource_level_jitter(
    costs: List[float],
    timestamps: List[str],
    resource_id: str,
    jitter_config: Dict[str, float],
) -> List[float]:
    """
    Apply resource-level jitter to create organic variations between resources.

    Args:
        costs: Base cost values (may already have variance/anomalies applied)
        timestamps: ISO timestamp strings
        resource_id: Unique resource identifier
        jitter_config: Jitter configuration parameters

    Returns:
        Enhanced costs with resource-level jitter applied
    """
    if not costs or not jitter_config:
        return costs

    # Parse jitter configuration
    intensity_jitter = jitter_config.get("intensity_jitter", 0.2)  # ±20% intensity
    phase_jitter_hours = jitter_config.get("phase_jitter_hours", 1.0)  # 0-1hr shift
    temporal_jitter = jitter_config.get("temporal_jitter", 0.1)  # ±10% timing
    resource_personality = jitter_config.get(
        "resource_personality", True
    )  # Enable personality

    enhanced_costs = costs.copy()

    # Apply intensity jitter (0.8x-1.2x range)
    if intensity_jitter > 0:
        intensity_multiplier = calculate_resource_intensity_jitter(
            resource_id, base_intensity=1.0, jitter_range=intensity_jitter
        )
        enhanced_costs = [cost * intensity_multiplier for cost in enhanced_costs]

    # Apply phase jitter (0-1hr phase shift)
    if phase_jitter_hours > 0:
        enhanced_costs = apply_phase_shift_jitter(
            enhanced_costs, timestamps, resource_id, phase_jitter_hours
        )

    # Apply temporal jitter (slight timing variations)
    if temporal_jitter > 0:
        enhanced_costs = apply_temporal_jitter(
            enhanced_costs, timestamps, resource_id, temporal_jitter
        )

    # Apply resource personality patterns
    if resource_personality:
        enhanced_costs = apply_resource_personality(
            enhanced_costs, timestamps, resource_id
        )

    # Normalize costs to preserve total cost (cost conservation)
    original_total = sum(costs)
    enhanced_total = sum(enhanced_costs)

    if enhanced_total > 0 and original_total > 0:
        normalization_factor = original_total / enhanced_total
        enhanced_costs = [cost * normalization_factor for cost in enhanced_costs]

    return enhanced_costs


def apply_phase_shift_jitter(
    costs: List[float], timestamps: List[str], resource_id: str, max_phase_hours: float
) -> List[float]:
    """
    Apply phase shift jitter to create timing variations between resources.

    Shifts the entire cost pattern by 0-1 hours to create organic timing differences.
    """
    if len(costs) < 2:
        return costs

    # Calculate phase shift for this resource
    phase_shift_hours = calculate_resource_phase_jitter(resource_id, max_phase_hours)
    phase_shift_periods = int(phase_shift_hours)  # Convert to hour periods

    if phase_shift_periods <= 0:
        return costs

    # Apply circular shift to create phase difference
    enhanced_costs = costs.copy()

    # Shift pattern by moving elements
    if phase_shift_periods < len(enhanced_costs):
        # Circular shift: move first N elements to end
        shifted_costs = (
            enhanced_costs[phase_shift_periods:] + enhanced_costs[:phase_shift_periods]
        )
        return shifted_costs

    return enhanced_costs


def apply_temporal_jitter(
    costs: List[float], timestamps: List[str], resource_id: str, jitter_factor: float
) -> List[float]:
    """
    Apply small temporal variations to create micro-timing differences.

    Adds small random adjustments to individual time periods.
    """
    np.random.seed(hash_resource_jitter_seed(resource_id, "temporal"))
    enhanced_costs = costs.copy()

    for i in range(len(enhanced_costs)):
        # Small random adjustment (±jitter_factor%)
        adjustment = np.random.uniform(-jitter_factor, jitter_factor)
        enhanced_costs[i] *= 1 + adjustment

    return enhanced_costs


def apply_resource_personality(
    costs: List[float], timestamps: List[str], resource_id: str
) -> List[float]:
    """
    Apply resource personality patterns based on resource characteristics.

    Different resource types get different behavioral patterns:
    - Compute instances: steady with occasional bursts
    - Storage: gradual growth patterns
    - Networking: traffic-based spikes
    - Databases: query-pattern variations
    """
    # Determine resource personality from resource_id
    personality = determine_resource_personality(resource_id)

    np.random.seed(hash_resource_jitter_seed(resource_id, "personality"))
    enhanced_costs = costs.copy()

    if personality == "bursty":
        # Occasional burst patterns for compute resources
        enhanced_costs = apply_burst_personality(enhanced_costs, resource_id)
    elif personality == "steady_growth":
        # Gradual growth for storage resources
        enhanced_costs = apply_growth_personality(enhanced_costs, resource_id)
    elif personality == "traffic_spikes":
        # Traffic-based patterns for networking
        enhanced_costs = apply_traffic_personality(enhanced_costs, resource_id)
    elif personality == "query_patterns":
        # Query-based variations for databases
        enhanced_costs = apply_database_personality(enhanced_costs, resource_id)
    else:
        # Default: stable with small variations
        enhanced_costs = apply_stable_personality(enhanced_costs, resource_id)

    return enhanced_costs


def determine_resource_personality(resource_id: str) -> str:
    """
    Determine resource personality based on resource ID characteristics.

    Args:
        resource_id: Resource identifier (e.g., "AWS_AmazonEC2_Compute Instance_instance")

    Returns:
        Personality type string
    """
    resource_lower = resource_id.lower()

    if any(
        keyword in resource_lower for keyword in ["database", "rds", "sql", "dynamo"]
    ) or resource_lower.endswith("_db"):
        return "query_patterns"
    elif any(
        keyword in resource_lower for keyword in ["compute", "ec2", "instance", "vm"]
    ):
        return "bursty"
    elif any(
        keyword in resource_lower for keyword in ["storage", "s3", "disk", "volume"]
    ):
        return "steady_growth"
    elif any(
        keyword in resource_lower
        for keyword in [
            "network",
            "bandwidth",
            "transfer",
            "load balancer",
            "loadbalancer",
            "elb",
        ]
    ):
        return "traffic_spikes"
    else:
        return "stable"


def apply_burst_personality(costs: List[float], resource_id: str) -> List[float]:
    """Apply bursty patterns for compute resources."""
    np.random.seed(hash_resource_jitter_seed(resource_id, "burst"))
    enhanced = costs.copy()

    # Add occasional burst events (5% chance per hour)
    for i in range(len(enhanced)):
        if np.random.random() < 0.05:  # 5% chance
            burst_multiplier = np.random.uniform(1.3, 2.0)  # 30-100% burst
            enhanced[i] *= burst_multiplier

    return enhanced


def apply_growth_personality(costs: List[float], resource_id: str) -> List[float]:
    """Apply steady growth patterns for storage resources."""
    np.random.seed(hash_resource_jitter_seed(resource_id, "growth"))
    enhanced = costs.copy()

    # Add gradual growth trend
    growth_rate = np.random.uniform(0.0001, 0.001)  # Small hourly growth

    for i in range(len(enhanced)):
        growth_factor = 1 + (growth_rate * i)
        enhanced[i] *= growth_factor

    return enhanced


def apply_traffic_personality(costs: List[float], resource_id: str) -> List[float]:
    """Apply traffic spike patterns for networking resources."""
    np.random.seed(hash_resource_jitter_seed(resource_id, "traffic"))
    enhanced = costs.copy()

    # Add traffic spike patterns (3% chance per hour)
    for i in range(len(enhanced)):
        if np.random.random() < 0.03:  # 3% chance
            spike_multiplier = np.random.uniform(1.5, 3.0)  # 50-200% spike
            enhanced[i] *= spike_multiplier

    return enhanced


def apply_database_personality(costs: List[float], resource_id: str) -> List[float]:
    """Apply query pattern variations for database resources."""
    np.random.seed(hash_resource_jitter_seed(resource_id, "database"))
    enhanced = costs.copy()

    # Add query pattern variations (business hours bias)
    for i, cost in enumerate(enhanced):
        hour_of_day = i % 24

        # Higher activity during business hours (9 AM - 5 PM)
        if 9 <= hour_of_day <= 17:
            business_multiplier = np.random.uniform(1.1, 1.4)
            enhanced[i] *= business_multiplier
        else:
            # Lower activity outside business hours
            off_hours_multiplier = np.random.uniform(0.7, 0.9)
            enhanced[i] *= off_hours_multiplier

    return enhanced


def apply_stable_personality(costs: List[float], resource_id: str) -> List[float]:
    """Apply stable patterns with small variations."""
    np.random.seed(hash_resource_jitter_seed(resource_id, "stable"))
    enhanced = costs.copy()

    # Add small random variations (±5%)
    for i in range(len(enhanced)):
        variation = np.random.uniform(-0.05, 0.05)
        enhanced[i] *= 1 + variation

    return enhanced


def create_resource_jitter_config(
    variance_profile: str, resource_characteristics: Dict[str, Any]
) -> Dict[str, float]:
    """
    Create resource jitter configuration based on variance profile and resource characteristics.

    Args:
        variance_profile: Name of the variance profile being used
        resource_characteristics: Resource metadata (cloud_provider, service, etc.)

    Returns:
        Jitter configuration dictionary
    """
    # Base jitter configuration
    base_config = {
        "intensity_jitter": 0.2,  # ±20% intensity variation
        "phase_jitter_hours": 1.0,  # 0-1hr phase shift
        "temporal_jitter": 0.1,  # ±10% timing variation
        "resource_personality": True,  # Enable personality patterns
    }

    # Adjust based on variance profile
    if "extreme" in variance_profile.lower():
        base_config["intensity_jitter"] = 0.3  # More variation for extreme profiles
        base_config["temporal_jitter"] = 0.15
    elif "minimal" in variance_profile.lower():
        base_config["intensity_jitter"] = 0.1  # Less variation for minimal profiles
        base_config["temporal_jitter"] = 0.05

    # Adjust based on resource type
    service = resource_characteristics.get("service", "").lower()
    if "compute" in service or "ec2" in service:
        base_config["intensity_jitter"] = min(
            base_config["intensity_jitter"] * 1.2, 0.4
        )
    elif "storage" in service or "s3" in service:
        base_config["phase_jitter_hours"] = 0.5  # Less phase variation for storage

    return base_config


def validate_resource_jitter_effectiveness(
    original_costs: List[float], jittered_costs: List[float], resource_id: str
) -> Dict[str, Any]:
    """
    Validate that resource jitter creates appropriate variations.

    Args:
        original_costs: Costs before jitter application
        jittered_costs: Costs after jitter application
        resource_id: Resource identifier

    Returns:
        Validation results for jitter effectiveness
    """
    if len(original_costs) != len(jittered_costs) or len(original_costs) == 0:
        return {"error": "Invalid input data"}

    # Calculate jitter statistics
    cost_differences = [
        abs(jittered - original) / original * 100
        for original, jittered in zip(original_costs, jittered_costs, strict=False)
        if original > 0
    ]

    avg_difference = (
        sum(cost_differences) / len(cost_differences) if cost_differences else 0
    )
    max_difference = max(cost_differences) if cost_differences else 0
    min_difference = min(cost_differences) if cost_differences else 0

    # Calculate total cost impact
    original_total = sum(original_costs)
    jittered_total = sum(jittered_costs)
    total_impact = (
        abs(jittered_total - original_total) / original_total * 100
        if original_total > 0
        else 0
    )

    return {
        "resource_id": resource_id,
        "avg_difference_pct": avg_difference,
        "max_difference_pct": max_difference,
        "min_difference_pct": min_difference,
        "total_cost_impact_pct": total_impact,
        "jitter_effectiveness": {
            "within_expected_range": 5
            <= avg_difference
            <= 25,  # 5-25% average difference
            "maintains_cost_bounds": total_impact <= 30,  # Total impact ≤ 30%
            "creates_variation": max_difference >= 10,  # At least 10% max difference
        },
    }