import json
import math
import re
from pathlib import Path
from typing import Dict, Any, List, Optional

from .base_module import BaseModule

class CarryWeightModule(BaseModule):
    
    def __init__(self):
        super().__init__()
        self.display_name = "Carry Weight Modifier"
        self.config_file = Path("config/defaults/predefined_carry_weights_config.json")
        self.files = {
            'CoreVariables.cfg': 'CoreVariables.cfg',
            'ObjEffectMaxParamsPrototypes.cfg': 'ObjEffectMaxParamsPrototypes.cfg',
            'ObjWeightParamsPrototypes.cfg': 'ObjWeightParamsPrototypes.cfg'
        }
    
    def get_predefined_configs(self) -> List[Dict[str, Any]]:
        configs = []
        
        if self.config_file.exists():
            with open(self.config_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
                
            for name, config in data.get("configurations", {}).items():
                weight = config["ObjWeightParamsPrototypes.cfg"]["max_inventory_mass"]
                configs.append({
                    'name': f"{weight} kg" + (" (Vanilla)" if name == "vanilla" else ""),
                    'config': config,
                    'weight': weight
                })
        
        return sorted(configs, key=lambda x: x['weight'])
    
    def get_custom_config(self) -> Optional[Dict[str, Any]]:
        print("\nCustom Carry Weight Configuration")
        print("=" * 40)
        print("Enter weight between 81-10000 kg")
        
        try:
            weight = int(input("Max carry weight: ").strip())
            
            if not 81 <= weight <= 10000:
                print("Weight must be between 81 and 10000")
                return None
            
            return self._calculate_config(weight)
            
        except ValueError:
            print("Invalid input")
            return None
    
    def _calculate_config(self, max_weight: int) -> Dict[str, Any]:
        if max_weight == 80:
            return {
                "CoreVariables.cfg": {
                    "inventory_penalty_less_weight": 50.0,
                    "medium_effect_start_ui": 50,
                    "critical_effect_start_ui": 70
                },
                "ObjEffectMaxParamsPrototypes.cfg": {
                    "penalty_less_weight_max": 90,
                    "additional_inventory_weight_max": 140
                },
                "ObjWeightParamsPrototypes.cfg": {
                    "max_inventory_mass": 80,
                    "inventory_penalty_less_weight": 49.99,
                    "thresholds": {
                        "no_effect": 80,
                        "velocity_change_1": 50,
                        "velocity_change_2": 60,
                        "velocity_change_3": 70
                    }
                }
            }
        
        penalty = math.floor(max_weight * 0.88 / 5) * 5
        critical = round(max_weight * 0.96 / 5) * 5
        velocity_2 = round((penalty + critical) / 2)
        
        return {
            "CoreVariables.cfg": {
                "inventory_penalty_less_weight": float(penalty),
                "medium_effect_start_ui": penalty,
                "critical_effect_start_ui": critical
            },
            "ObjEffectMaxParamsPrototypes.cfg": {
                "penalty_less_weight_max": 9999,
                "additional_inventory_weight_max": 9999
            },
            "ObjWeightParamsPrototypes.cfg": {
                "max_inventory_mass": max_weight,
                "inventory_penalty_less_weight": penalty - 0.01,
                "thresholds": {
                    "no_effect": max_weight,
                    "velocity_change_1": penalty,
                    "velocity_change_2": velocity_2,
                    "velocity_change_3": critical
                }
            }
        }
    
    def apply_configuration(self, config: Dict[str, Any], output_path: Path) -> bool:
        try:
            game_data_path = output_path / "Stalker2/Content/GameLite/GameData"
            game_data_path.mkdir(parents=True, exist_ok=True)
            
            self._apply_core_variables(config["CoreVariables.cfg"], game_data_path)
            self._apply_effect_params(config["ObjEffectMaxParamsPrototypes.cfg"], game_data_path)
            self._apply_weight_params(config["ObjWeightParamsPrototypes.cfg"], game_data_path)
            
            return True
            
        except Exception as e:
            print(f"Error applying configuration: {e}")
            return False
    
    def _apply_core_variables(self, config: Dict[str, Any], output_path: Path):
        output_file = output_path / "CoreVariables.cfg"
        
        if output_file.exists():
            source_file = output_file
        else:
            source_file = self.find_file_in_extraction("CoreVariables.cfg")
            if not source_file:
                raise FileNotFoundError("CoreVariables.cfg not found in extraction")
        
        with open(source_file, 'r', encoding='utf-8') as f:
            content = f.read()
        
        for key, value in [
            ('InventoryPenaltyLessWeight', config["inventory_penalty_less_weight"]),
            ('MediumEffectStartUI', config["medium_effect_start_ui"]),
            ('CriticalEffectStartUI', config["critical_effect_start_ui"])
        ]:
            content = re.sub(rf'(\s{key}\s*=\s*)[^\n]*', rf'\g<1>{value}', content)
        
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(content)
    
    def _apply_effect_params(self, config: Dict[str, Any], output_path: Path):
        source_file = self.find_file_in_extraction("ObjEffectMaxParamsPrototypes.cfg")
        if not source_file:
            raise FileNotFoundError("ObjEffectMaxParamsPrototypes.cfg not found in extraction")
        
        output_file = output_path / "ObjEffectMaxParamsPrototypes.cfg"
        
        with open(source_file, 'r', encoding='utf-8') as f:
            content = f.read()
        
        content = re.sub(
            r'(EffectSID\s*=\s*EEffectType::PenaltyLessWeight[\s\S]*?MaxValue\s*=\s*)[^\n]*',
            rf'\g<1>{config["penalty_less_weight_max"]}', content
        )
        content = re.sub(
            r'(EffectSID\s*=\s*EEffectType::AdditionalInventoryWeight[\s\S]*?MaxValue\s*=\s*)[^\n]*',
            rf'\g<1>{config["additional_inventory_weight_max"]}', content
        )
        
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(content)
    
    def _apply_weight_params(self, config: Dict[str, Any], output_path: Path):
        source_file = self.find_file_in_extraction("ObjWeightParamsPrototypes.cfg")
        if not source_file:
            raise FileNotFoundError("ObjWeightParamsPrototypes.cfg not found in extraction")
        
        output_file = output_path / "ObjWeightParamsPrototypes.cfg"
        
        with open(source_file, 'r', encoding='utf-8') as f:
            lines = f.readlines()
        
        thresholds = config["thresholds"]
        threshold_values = [
            thresholds["no_effect"],
            thresholds["velocity_change_3"],
            thresholds["velocity_change_2"],
            thresholds["velocity_change_1"]
        ]
        
        threshold_index = 0
        inside_default = False
        struct_depth = 0
        
        for i, line in enumerate(lines):
            stripped = line.strip()
            
            if not inside_default and re.match(r'DefaultWeightParams\s*:\s*struct\.begin', stripped):
                inside_default = True
                struct_depth = 1
                continue
            
            if inside_default:
                if 'struct.begin' in stripped:
                    struct_depth += 1
                
                if re.match(r'Threshold\s*=\s*[\d\.]*f?', stripped) and threshold_index < len(threshold_values):
                    lines[i] = re.sub(
                        r'(Threshold\s*=\s*)[\d\.]*f?',
                        rf'\g<1>{threshold_values[threshold_index]}.f',
                        line
                    )
                    threshold_index += 1
                
                if re.match(r'MaxInventoryMass\s*=\s*\d*', stripped):
                    lines[i] = re.sub(
                        r'(MaxInventoryMass\s*=\s*)\d*',
                        rf'\g<1>{config["max_inventory_mass"]}',
                        line
                    )
                
                if re.match(r'InventoryPenaltyLessWeight\s*=\s*[\d\.]*$', stripped):
                    lines[i] = re.sub(
                        r'(InventoryPenaltyLessWeight\s*=\s*)[\d\.]*',
                        rf'\g<1>{config["inventory_penalty_less_weight"]}',
                        line
                    )
                
                if 'struct.end' in stripped:
                    struct_depth -= 1
                    if struct_depth <= 0:
                        inside_default = False
                        break
        
        with open(output_file, 'w', encoding='utf-8') as f:
            f.writelines(lines)