import pygame
import math

BASE_TILE = 50


class Boss2(pygame.sprite.Sprite):
    """2x2 tile boss that chases player and occasionally performs a dash toward player's last position."""

    def __init__(self, x, y, hp=25):
        super().__init__()
        self.max_hp = hp
        self.hp = hp
        self.dead = False

        # Load base texture (boss2.png) with Windows/macOS friendly paths
        img = None
        for p in (
            r"Textures/boss2.png",
            r"Textures\boss2.png",
            r"../Textures/boss2.png",
            r"..\Textures\boss2.png",
        ):
            try:
                img = pygame.image.load(p).convert_alpha()
                break
            except Exception:
                continue
        if img is None:
            img = pygame.Surface((BASE_TILE, BASE_TILE), pygame.SRCALPHA)
            img.fill((150, 80, 80))
        self._base_img = img

        # Initial image/rect (resized on first update via tile_size)
        self.image = img.copy()
        self.rect = self.image.get_rect(center=(x, y))

        # Movement base speeds (scaled by tile_size / BASE_TILE)
        self.chase_speed = 2.5    # faster normal follow speed
        self.dash_speed = self.chase_speed * 3.0  # dash is 3x faster than chase

        # State machine
        self.state = "chase"      # chase | dash_prep | dash
        self.dash_prep_ms = 1500  # 1.5 seconds standing still
        self.state_change_time = pygame.time.get_ticks()
        self.dash_target = None   # (x,y) player's last position
        self.last_dash_end_ms = 0
        self.dash_cooldown_ms = 2500  # time between dash attempts

        # Drawing cache — keep rotated+scaled image per (scale, angle)
        # keys: "scale", "angle_key", "image" (already scaled+rotated)
        self._draw_cache = {"scale": None, "angle_key": None, "image": None}
        self._last_tile_size = None

        # store the base image scaled to logic-space (2x2 tiles)
        self.base_scaled = None

        # Facing angle in degrees (toward player)
        self.angle_deg = 0.0

    def _ensure_scaled_size(self, world):
        """Scale to 2x2 tiles using world.tile_size and keep base_scaled for rotation."""
        tile_size = getattr(world, "tile_size", BASE_TILE)
        if self._last_tile_size == tile_size and self.base_scaled is not None:
            return
        self._last_tile_size = tile_size

        w = tile_size * 2
        h = tile_size * 2
        try:
            self.base_scaled = pygame.transform.smoothscale(self._base_img, (w, h))
        except Exception:
            self.base_scaled = pygame.transform.scale(self._base_img, (w, h))

        # set default current image reference (keeps rect centered)
        center = self.rect.center
        self.image = self.base_scaled
        self.rect = self.image.get_rect(center=center)

        # invalidate draw cache so rotated/scaled image is rebuilt
        self._draw_cache["scale"] = None
        self._draw_cache["angle_key"] = None
        self._draw_cache["image"] = None

    def _take_player_projectile_hits(self, player_projectiles):
        for proj in list(player_projectiles):
            try:
                hit_rect = proj.rect
            except Exception:
                continue
            if self.rect.colliderect(hit_rect):
                dmg = getattr(proj, "damage", 1)
                self.hp = max(0, self.hp - max(0, int(dmg)))
                try:
                    proj.kill()
                except Exception:
                    pass
                if self.hp <= 0:
                    self.dead = True
                    self.kill()
                    return

    def _damage_player_on_touch(self, player):
        if self.rect.colliderect(player.rect):
            try:
                player.take_damage(1)
            except Exception:
                player.health = max(0, getattr(player, "health", 0) - 1)

    def _move_towards(self, tx, ty, speed_px):
        dx = tx - self.rect.centerx
        dy = ty - self.rect.centery
        dist = math.hypot(dx, dy)
        if dist == 0:
            return True
        if dist <= speed_px:
            self.rect.center = (int(round(tx)), int(round(ty)))
            return True
        self.rect.centerx += dx / dist * speed_px
        self.rect.centery += dy / dist * speed_px
        return False

    def update(self, player, player_projectiles, world):
        """Update movement/state and collisions."""
        self._ensure_scaled_size(world)

        # update angle so boss always looks at the player (store degrees)
        try:
            dx = player.rect.centerx - self.rect.centerx
            dy = player.rect.centery - self.rect.centery
            self.angle_deg = math.degrees(math.atan2(dy, dx))  # 0 -> right, increases CCW
        except Exception:
            pass

        now = pygame.time.get_ticks()
        tile_size = getattr(world, "tile_size", BASE_TILE)
        s = float(tile_size) / float(BASE_TILE)
        chase_px = max(0.3, self.chase_speed * s)
        dash_px = max(0.8, self.dash_speed * s)

        # Take damage from player projectiles
        self._take_player_projectile_hits(player_projectiles)
        if self.dead:
            return

        # State machine
        if self.state == "chase":
            # Move toward player's current position
            self._move_towards(player.rect.centerx, player.rect.centery, chase_px)

            # Occasionally start dash prep if cooldown passed
            if now - self.last_dash_end_ms >= self.dash_cooldown_ms:
                # Begin standing still and remember target
                self.state = "dash_prep"
                self.state_change_time = now
                # store player's last known position for dash target
                self.dash_target = (player.rect.centerx, player.rect.centery)

        elif self.state == "dash_prep":
            # Stand still during prep; only update target periodically if you want,
            # but spec says "dash will determine players last colation" -> keep recorded position.
            if now - self.state_change_time >= self.dash_prep_ms and self.dash_target is not None:
                self.state = "dash"

        elif self.state == "dash":
            if self.dash_target is None:
                self.state = "chase"
                self.last_dash_end_ms = now
            else:
                reached = self._move_towards(self.dash_target[0], self.dash_target[1], dash_px)
                if reached:
                    self.state = "chase"
                    self.last_dash_end_ms = now
                    self.dash_target = None

        # Clamp to world bounds
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > world.pixel_width:
            self.rect.right = world.pixel_width
        if self.rect.top < 0:
            self.rect.top = 0
        if self.rect.bottom > world.pixel_height:
            self.rect.bottom = world.pixel_height

        # Contact damage
        self._damage_player_on_touch(player)

    def draw(self, screen, scale, off_x=0, off_y=0):
        # build a rotated+scaled image and cache it per (scale, angle_key)
        if self.base_scaled is None:
            return  # nothing to draw

        # quantize angle to integer to keep cache stable
        angle_key = int(round(self.angle_deg)) % 360

        cache = self._draw_cache
        if cache["scale"] != scale or cache["angle_key"] != angle_key or cache["image"] is None:
            cache["scale"] = scale
            cache["angle_key"] = angle_key
            # rotate base (logic-space) then scale to screen
            try:
                rotated = pygame.transform.rotate(self.base_scaled, -self.angle_deg)
            except Exception:
                rotated = pygame.transform.rotate(self.base_scaled, -float(angle_key))
            sw = max(1, int(round(rotated.get_width() * scale)))
            sh = max(1, int(round(rotated.get_height() * scale)))
            try:
                cache["image"] = pygame.transform.smoothscale(rotated, (sw, sh))
            except Exception:
                cache["image"] = pygame.transform.scale(rotated, (sw, sh))

        img = cache["image"]
        # compute screen position from logic-space rect
        sx = int(round(self.rect.x * scale)) + off_x
        sy = int(round(self.rect.y * scale)) + off_y
        # rotated image is centered around boss center; adjust blit so visual center matches rect center
        img_w, img_h = img.get_width(), img.get_height()
        center_x = int(round(self.rect.centerx * scale)) + off_x
        center_y = int(round(self.rect.centery * scale)) + off_y
        screen.blit(img, (center_x - img_w // 2, center_y - img_h // 2))

        # Simple HP bar (position above the sprite)
        bw = max(40, img_w)
        bh = max(3, int(round(5 * scale)))
        bx = center_x - bw // 2
        by = center_y - img_h // 2 - bh - max(2, int(round(5 * scale)))
        pygame.draw.rect(screen, (40, 40, 40), (bx, by, bw, bh))
        ratio = 0 if self.max_hp <= 0 else self.hp / self.max_hp
        pygame.draw.rect(screen, (220, 40, 40), (bx, by, int(round(bw * ratio)), bh))
        pygame.draw.rect(screen, (0, 0, 0), (bx, by, bw, bh), max(1, int(round(2 * scale))))
