import logging
import os
import time
from typing import Dict, Any, List, Optional
from PIL import Image, ImageDraw, ImageFont

logger = logging.getLogger(__name__)

class LeaderboardManager:
    """
    Generic leaderboard renderer: scrolls a league strip of:
    [league logo] [conference label] [#rank] [team logo] [abbr] [record (optional, spacing preserved)]
    League-specific data is injected via league_data from the league manager.
    """

    def __init__(self, config: Dict[str, Any], display_manager, league_data: Dict[str, Any]):
        self.leaderboard_config = config.get("leaderboard", {})
        self.is_enabled = self.leaderboard_config.get("enabled", False)
        self.update_interval = self.leaderboard_config.get("update_interval", 3600)
        self.scroll_speed = max(1, self.leaderboard_config.get("scroll_speed", 1))
        self.scroll_delay = self.leaderboard_config.get("scroll_delay", 0.01)
        self.loop = self.leaderboard_config.get("loop", False)
        self.show_record = self.leaderboard_config.get("show_record", True)

        self.display_manager = display_manager
        self.league_data = league_data
        self.fonts = self._load_fonts()

        self._cached_standings: Optional[Dict[str, List[Dict[str, Any]]]] = None
        self._last_standings_reload: float = 0.0

    def _load_fonts(self) -> Dict[str, ImageFont.FreeTypeFont]:
        try:
            return {
                'small':  ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 6),
                'medium': ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8),
                'large':  ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 10),
                'xlarge': ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 12),
            }
        except IOError:
            logger.warning("Custom fonts not found, falling back to default PIL font.")
            return {k: ImageFont.load_default() for k in ['small','medium','large','xlarge']}

    def _get_team_logo(self, logo_path: str) -> Optional[Image.Image]:
        try:
            if logo_path and os.path.exists(logo_path):
                logo = Image.open(logo_path).convert("RGBA")
                bbox = logo.getbbox()
                if bbox:
                    logo = logo.crop(bbox)
                max_h = self.display_manager.height
                max_w = int(max_h * 1.5)
                scale = min(max_w / logo.width, max_h / logo.height)
                return logo.resize((int(logo.width * scale), int(logo.height * scale)), Image.Resampling.LANCZOS)
        except Exception as e:
            logger.error(f"Error loading logo {logo_path}: {e}")
        return None

    def _fetch_standings(self) -> Dict[str, List[Dict[str, Any]]]:
        now = time.time()
        if self._cached_standings and (now - self._last_standings_reload < self.update_interval):
            return self._cached_standings

        provider = self.league_data.get("standings_provider")
        try:
            standings = provider() if callable(provider) else self.league_data.get("standings", {})
            if isinstance(standings, dict):
                self._cached_standings = standings
                self._last_standings_reload = now
                logger.info(f"[Leaderboard] Standings reloaded: {list(standings.keys())}")
                return standings
        except Exception as e:
            logger.error(f"[Leaderboard] standings_provider failed: {e}")

        return self._cached_standings or {}

    def display(self):
        if not self.is_enabled:
            logger.info("[Leaderboard] Disabled in config")
            return

        standings = self._fetch_standings()
        conferences: List[str] = self.league_data.get("conferences", [])
        if not conferences or not any(standings.get(conf) for conf in conferences):
            logger.info("[Leaderboard] No conferences or empty standings")
            return

        strip_height = self.display_manager.height
        logo_spacing = self.leaderboard_config.get("logo_spacing", 20)
        conference_spacing = self.leaderboard_config.get("conference_spacing", 60)

        # --- Measure total width ---
        dummy_img = Image.new("RGB", (1, 1))
        draw = ImageDraw.Draw(dummy_img)
        total_width = 0

        league_logo_path = self.league_data.get("league_logo", "")
        league_logo_probe = self._get_team_logo(league_logo_path)
        if league_logo_probe:
            total_width += league_logo_probe.width + logo_spacing

        for conf in conferences:
            teams = standings.get(conf, [])
            if not teams:
                continue
            conf_text = f"{conf.capitalize()} Div."
            total_width += int(draw.textlength(conf_text, font=self.fonts['medium'])) + 40
            for rank, team in enumerate(teams, start=1):
                total_width += int(draw.textlength(f"#{rank}.", font=self.fonts['xlarge'])) + 5
                total_width += int(strip_height * 1.5) + 5  # logo box space
                abbr = team.get("abbreviation", team.get("name", "")[:3])
                total_width += int(draw.textlength(abbr, font=self.fonts['large'])) + 10
                record = team.get("record", "")
                total_width += (int(draw.textlength(record, font=self.fonts['medium'])) + 30) if record else 30
            total_width += conference_spacing

        # --- Build strip ---
        strip = Image.new("RGB", (total_width, strip_height), (0, 0, 0))
        draw = ImageDraw.Draw(strip)
        x_offset = 0

        league_logo = self._get_team_logo(league_logo_path)
        if league_logo:
            strip.paste(league_logo, (x_offset, (strip_height - league_logo.height) // 2), league_logo)
            x_offset += league_logo.width + logo_spacing

        for conf in conferences:
            teams = standings.get(conf, [])
            if not teams:
                continue

            conf_text = f"{conf.capitalize()} Div."
            conf_y = (strip_height - (self.fonts['medium'].getbbox(conf_text)[3])) // 2
            draw.text((x_offset, conf_y), conf_text, font=self.fonts['medium'], fill=(0, 200, 255))
            x_offset += int(draw.textlength(conf_text, font=self.fonts['medium'])) + 40

            for rank, team in enumerate(teams, start=1):
                rank_text = f"#{rank}."
                rank_y = (strip_height - (self.fonts['xlarge'].getbbox(rank_text)[3])) // 2
                draw.text((x_offset, rank_y), rank_text, font=self.fonts['xlarge'], fill=(255, 255, 0))
                x_offset += int(draw.textlength(rank_text, font=self.fonts['xlarge'])) + 5

                logo = self._get_team_logo(team.get("logo", ""))
                if logo:
                    strip.paste(logo, (x_offset, (strip_height - logo.height) // 2), logo)
                    x_offset += logo.width + 5
                else:
                    # Reserve logo space even if missing, for consistent layout
                    x_offset += int(strip_height * 1.5) + 5

                abbr = team.get("abbreviation", team.get("name", "")[:3])
                abbr_y = (strip_height - (self.fonts['large'].getbbox(abbr)[3])) // 2
                draw.text((x_offset, abbr_y), abbr, font=self.fonts['large'], fill=(255, 255, 255))
                x_offset += int(draw.textlength(abbr, font=self.fonts['large'])) + 10

                record = team.get("record", "")
                if self.show_record and record:
                    rec_y = (strip_height - (self.fonts['medium'].getbbox(record)[3])) // 2
                    draw.text((x_offset, rec_y), record, font=self.fonts['medium'], fill=(255, 255, 0))
                    x_offset += int(draw.textlength(record, font=self.fonts['medium'])) + 30
                else:
                    x_offset += 30

            x_offset += conference_spacing

        # --- Scroll ---
        visible_w = self.display_manager.width
        intro_hold = self.leaderboard_config.get("logo_intro_hold", 0)

        if self.leaderboard_config.get("dynamic_duration", False):
            est_duration = (total_width / self.scroll_speed) * self.scroll_delay
            duration = max(
                self.leaderboard_config.get("min_duration", 30),
                min(est_duration, self.leaderboard_config.get("max_display_time", 600)),
            )
        else:
            duration = self.leaderboard_config.get("max_display_time", 600)

        end_time = time.time() + duration

        def scroll_once():
            for start_x in range(-visible_w, x_offset, self.scroll_speed):
                # Crop the appropriate segment from the strip
                frame = strip.crop((max(0, start_x), 0, start_x + visible_w, strip_height))
                # Create a visible frame canvas and paste the cropped segment at the right offset
                frame_canvas = Image.new("RGB", (visible_w, strip_height), (0, 0, 0))
                paste_x = 0 if start_x >= 0 else -start_x
                frame_canvas.paste(frame, (paste_x, 0))

                # Paint to the matrix
                self.display_manager.image.paste(frame_canvas, (0, 0))
                self.display_manager.update_display()

                # Optional intro hold when the strip first fully aligns
                if start_x == 0 and intro_hold > 0:
                    time.sleep(intro_hold)

                time.sleep(self.scroll_delay)

        while True:
            scroll_once()
            if not self.loop or time.time() >= end_time:
                break

        # --- Static league logo hold at the end ---
        if league_logo:
            canvas = Image.new("RGB", (visible_w, strip_height), (0, 0, 0))
            offset_x = (visible_w - league_logo.width) // 2
            offset_y = (strip_height - league_logo.height) // 2
            canvas.paste(league_logo, (offset_x, offset_y), league_logo)
            self.display_manager.image.paste(canvas, (0, 0))
            self.display_manager.update_display()
            time.sleep(self.leaderboard_config.get("logo_hold", 3))
