import pygame
from client.resource_path import get_asset_path
import os

# Define a smoothing factor for client-side interpolation
LERP_FACTOR = 0.15

class Enemy:
    def __init__(self, enemy_data, sprite_sheet=None):
        """
        Initializes the enemy on the client from spawn data.
        Spawn data includes actions (animations) which updates don't have.
        """
        self.id = enemy_data.get("id")
        self.name = enemy_data.get("name")

        # --- Positional State (from server) ---
        self.x = enemy_data.get("x", 0)
        self.y = enemy_data.get("y", 0)
        self.map_id = enemy_data.get("map_id")
        self.target_x = self.x 
        self.target_y = self.y
        
        # --- Combat & Behavior Props (from server) ---
        self.state = enemy_data.get("state", "idle")
        self.direction = enemy_data.get("direction", "left")
        self.health = enemy_data.get("health", 1)
        self.max_health = enemy_data.get("max_health", 1)
        
        # --- Get actions from spawn data ---
        self.actions = enemy_data.get("actions", {})
        self.is_boss = enemy_data.get("is_boss", False)
        
        # --- Sprite Scaling (from server seed data) ---
        # 1.0 = normal size, 2.0 = double size, 0.5 = half size
        self.sprite_scale = enemy_data.get("sprite_scale", 1.0)  # Get from server
    
        # --- Animation Initialization ---
        self.sprite_sheet = sprite_sheet
        self.frame_index = 0
        self.frame_timer = 0
        self.frames = []
        
        # Map 'patrol' state to 'walk' animation if 'patrol' is not defined
        animation_state = self._get_animation_state(self.state)
        current_action_defs = self.actions.get(animation_state, {})
        self.frame_duration = current_action_defs.get("frame_duration", 120)

        # Load Sprite Sheet and Frames
        sprite_path = current_action_defs.get('sprite_path')
        if sprite_path:
            self.sprite_sheet = load_enemy_sprite(sprite_path) 
            if self.sprite_sheet:
                self._load_current_animation_frames(animation_state)
            else:
                print(f"[Error] Enemy {self.name}: Failed to load sprite sheet for path {sprite_path}")
        else:
            print(f"[Error] Enemy {self.name}: No sprite path defined for state '{animation_state}'")

    def _get_animation_state(self, state):
        """Map game state to animation state (e.g., 'patrol' -> 'walk')."""
        state_mapping = {
            "patrol": "walk",
            "chase": "walk",
            "attack": "idle",  # or create an attack animation
        }
        return state_mapping.get(state, state)

    def _load_current_animation_frames(self, animation_state):
        """Helper to slice the loaded sheet into frames for the current state."""
        if not self.sprite_sheet:
            self.frames = []
            print(f"[Error] Enemy {self.name}: No sprite sheet available for state '{animation_state}'")
            return

        current_action_defs = self.actions.get(animation_state, {})
        frame_count = current_action_defs.get("frame_count") 

        if not frame_count:
            print(f"[Warning] Enemy {self.name}: Frame count missing for state '{animation_state}'.")
            self.frames = []
            return

        try:
            self.load_frames(self.sprite_sheet, frame_count)
            self.frame_index = 0
            self.frame_timer = 0
            print(f"[SUCCESS] Enemy {self.name}: Loaded {len(self.frames)} frames for state '{animation_state}'")
        except Exception as e:
            print(f"[Error] Enemy {self.name}: Failed to load frames for state '{animation_state}': {e}")
            self.frames = []

    def sync_state(self, server_data: dict):
        """
        Receives position update from the server and sets it as the target.
        Only updates runtime data (position, state, health) - NOT actions.
        Update packets don't include 'actions', so we keep using the ones from spawn.
        """
        self.target_x = server_data.get("x", self.target_x) 
        self.target_y = server_data.get("y", self.target_y)
        
        new_state = server_data.get("state", self.state)
        
        if new_state != self.state:
            self.state = new_state
            # Map state to animation (e.g., 'patrol' -> 'walk')
            animation_state = self._get_animation_state(self.state)
            current_action_defs = self.actions.get(animation_state, {})
            self.frame_duration = current_action_defs.get("frame_duration", 120)
            self._load_current_animation_frames(animation_state)
            
        self.direction = server_data.get("direction", self.direction)
        self.health = server_data.get("health", self.health)
        self.max_health = server_data.get("max_health", self.max_health)

    def load_frames(self, sprite_sheet, frame_count):
        """
        Split sprite_sheet into individual frames using the provided frame_count.
        """
        sheet_width, sheet_height = sprite_sheet.get_size()
        
        if frame_count <= 0:
            raise ValueError(f"Frame count is zero or negative ({frame_count})")
        
        frame_width = sheet_width // frame_count
        if frame_width * frame_count != sheet_width:
            print(f"[Warning] Enemy {self.name}: Sprite sheet width ({sheet_width}) not evenly divisible by frame_count ({frame_count}). Frame width: {frame_width}")

        self.frames = []
        for i in range(frame_count):
            try:
                frame = sprite_sheet.subsurface(pygame.Rect(i * frame_width, 0, frame_width, sheet_height))
                self.frames.append(frame)
            except ValueError as e:
                print(f"[Error] Enemy {self.name}: Failed to extract frame {i}: {e}")
                raise

    def update(self, dt):
        """
        dt: milliseconds since last frame
        Handles movement smoothing and animation updates.
        """
        if abs(self.x - self.target_x) > 1:
            self.x += (self.target_x - self.x) * LERP_FACTOR
        else:
            self.x = self.target_x
            
        if abs(self.y - self.target_y) > 1:
            self.y += (self.target_y - self.y) * LERP_FACTOR
        else:
            self.y = self.target_y

        if self.frames:
            self.frame_timer += dt
            if self.frame_timer >= self.frame_duration:
                self.frame_timer = 0
                self.frame_index = (self.frame_index + 1) % len(self.frames)
    
    def draw(self, surface, camera_x=0, camera_y=0):
        """
        Draw enemy on the given surface. camera_x/y offset for scrolling.
        Center sprite on collision box for proper alignment.
        """
        # Collision box dimensions (matches server default)
        collision_width = getattr(self, 'collision_width', 32)
        collision_height = getattr(self, 'collision_height', 32)
        
        # Base position (top-left of collision box)
        draw_x = self.x - camera_x
        draw_y = self.y - camera_y

        if self.frames:
            frame = self.frames[self.frame_index]
            if self.direction == "left":
                frame = pygame.transform.flip(frame, True, False)
            
            # Apply scaling
            original_width, original_height = frame.get_size()
            scaled_width = int(original_width * self.sprite_scale)
            scaled_height = int(original_height * self.sprite_scale)
            frame = pygame.transform.scale(frame, (scaled_width, scaled_height))
            
            # Get scaled sprite dimensions
            sprite_width, sprite_height = frame.get_size()
            
            # Center sprite horizontally on collision box
            sprite_draw_x = draw_x - (sprite_width - collision_width) // 2
            
            # Align sprite bottom with collision box bottom
            sprite_draw_y = draw_y + collision_height - sprite_height
            
            surface.blit(frame, (sprite_draw_x, sprite_draw_y))
        else:
            # Fallback rectangle if no frames loaded
            color = (255, 0, 0) if self.is_boss else (0, 255, 0)
            pygame.draw.rect(surface, color, pygame.Rect(draw_x, draw_y, collision_width, collision_height))

        # Draw health bar centered above collision box
        bar_width = collision_width
        bar_height = 4
        health_ratio = max(0, self.health / self.max_health)
        pygame.draw.rect(surface, (0, 0, 0), (draw_x, draw_y - 10, bar_width, bar_height))
        pygame.draw.rect(surface, (255, 0, 0), (draw_x, draw_y - 10, bar_width * health_ratio, bar_height))

def load_enemy_sprite(sprite_path: str):
    """
    Load the sprite sheet from the server-provided path.
    """
    if not sprite_path:
        print("[Error] Sprite path is empty.")
        return None
    
    try:
        script_dir = os.path.dirname(os.path.abspath(__file__))
        project_root = os.path.abspath(os.path.join(script_dir, '..')) 
        normalized_sprite_path = os.path.normpath(sprite_path)
        absolute_path = os.path.join(project_root, normalized_sprite_path)

        image = pygame.image.load(absolute_path)
        loaded_image = image.convert_alpha()
        
        if loaded_image.get_size() == (0, 0):
            print(f"[Error] Pygame loaded image but size is (0, 0) at: {absolute_path}")
            return None
        
        print(f"[SUCCESS] Loaded sprite sheet: {absolute_path} (Size: {loaded_image.get_size()})")
        return loaded_image
        
    except FileNotFoundError:
        print(f"[Error] FileNotFoundError: Failed to find sprite at the constructed path: {absolute_path}")
        return None
    except pygame.error as e:
        print(f"[PygameError] Failed to load sprite at '{absolute_path}': {e}")
        return None
    except Exception as e:
        print(f"[Error] An unexpected error occurred while loading sprite '{absolute_path}': {e}")
        return None