"""
Player Service - Dependency Injection Version
Business logic for player operations with injectable dependencies.
"""

import logging
from typing import List, Optional, Dict, Any
from peewee import fn as peewee_fn

from .base import BaseService
from .interfaces import AbstractPlayerRepository, QueryResult

logger = logging.getLogger('discord_app')


class PlayerService(BaseService):
    """Service for player-related operations with dependency injection."""
    
    cache_patterns = [
        "players*",
        "players-search*",
        "player*",
        "team-roster*"
    ]
    
    def __init__(
        self,
        player_repo: Optional[AbstractPlayerRepository] = None,
        **kwargs
    ):
        """
        Initialize PlayerService with optional repository.
        
        Args:
            player_repo: AbstractPlayerRepository implementation (mock or real)
            **kwargs: Additional arguments passed to BaseService
        """
        super().__init__(player_repo=player_repo, **kwargs)
        self._player_repo = player_repo
    
    @property
    def player_repo(self) -> AbstractPlayerRepository:
        """Get the player repository, using real DB if not injected."""
        if self._player_repo is not None:
            return self._player_repo
        # Fall back to real DB models for production
        from ..db_engine import Player
        self._Player_model = Player
        return self._get_real_repo()
    
    def _get_real_repo(self) -> 'RealPlayerRepository':
        """Get a real DB repository for production use."""
        from ..db_engine import Player
        return RealPlayerRepository(Player)
    
    def get_players(
        self,
        season: Optional[int] = None,
        team_id: Optional[List[int]] = None,
        pos: Optional[List[str]] = None,
        strat_code: Optional[List[str]] = None,
        name: Optional[str] = None,
        is_injured: Optional[bool] = None,
        sort: Optional[str] = None,
        short_output: bool = False,
        as_csv: bool = False
    ) -> Dict[str, Any]:
        """
        Get players with filtering and sorting.
        
        Args:
            season: Filter by season
            team_id: Filter by team IDs
            pos: Filter by positions
            strat_code: Filter by strat codes
            name: Filter by name (exact match)
            is_injured: Filter by injury status
            sort: Sort order
            short_output: Exclude related data
            as_csv: Return as CSV format
            
        Returns:
            Dict with count and players list, or CSV string
        """
        try:
            # Get base query from repo
            if season is not None:
                query = self.player_repo.select_season(season)
            else:
                query = self.player_repo.select_season(0)
            
            # Apply filters using repo-agnostic approach
            query = self._apply_player_filters(
                query,
                team_id=team_id,
                pos=pos,
                strat_code=strat_code,
                name=name,
                is_injured=is_injured
            )
            
            # Apply sorting
            query = self._apply_player_sort(query, sort)
            
            # Convert to list of dicts
            players_data = self._query_to_player_dicts(query, short_output)
            
            # Return format
            if as_csv:
                return self._format_player_csv(players_data)
            else:
                return {
                    "count": len(players_data),
                    "players": players_data
                }
                
        except Exception as e:
            self.handle_error(f"Error fetching players: {e}", e)
        finally:
            self.close_db()
    
    def _apply_player_filters(
        self,
        query: QueryResult,
        team_id: Optional[List[int]] = None,
        pos: Optional[List[str]] = None,
        strat_code: Optional[List[str]] = None,
        name: Optional[str] = None,
        is_injured: Optional[bool] = None
    ) -> QueryResult:
        """Apply player filters in a repo-agnostic way."""
        
        # Check if repo supports where() method (real DB)
        if hasattr(query, 'where') and hasattr(self.player_repo, 'select_season'):
            # Use DB-native filtering for real repos
            from ..db_engine import Player
            
            if team_id:
                query = query.where(Player.team_id << team_id)
            
            if strat_code:
                code_list = [x.lower() for x in strat_code]
                query = query.where(peewee_fn.Lower(Player.strat_code) << code_list)
            
            if name:
                query = query.where(peewee_fn.lower(Player.name) == name.lower())
            
            if pos:
                p_list = [x.upper() for x in pos]
                pos_conditions = (
                    (Player.pos_1 << p_list) |
                    (Player.pos_2 << p_list) |
                    (Player.pos_3 << p_list) |
                    (Player.pos_4 << p_list) |
                    (Player.pos_5 << p_list) |
                    (Player.pos_6 << p_list) |
                    (Player.pos_7 << p_list) |
                    (Player.pos_8 << p_list)
                )
                query = query.where(pos_conditions)
            
            if is_injured is not None:
                if is_injured:
                    query = query.where(Player.il_return.is_null(False))
                else:
                    query = query.where(Player.il_return.is_null(True))
        else:
            # Use Python filtering for mocks
            def matches(player):
                if team_id and player.get('team_id') not in team_id:
                    return False
                if strat_code:
                    code_list = [s.lower() for s in strat_code]
                    player_code = (player.get('strat_code') or '').lower()
                    if player_code not in code_list:
                        return False
                if name and (player.get('name') or '').lower() != name.lower():
                    return False
                if pos:
                    p_list = [p.upper() for p in pos]
                    player_pos = [
                        player.get(f'pos_{i}') for i in range(1, 9)
                        if player.get(f'pos_{i}')
                    ]
                    if not any(p in p_list for p in player_pos):
                        return False
                if is_injured is not None:
                    has_injury = player.get('il_return') is not None
                    if is_injured and not has_injury:
                        return False
                    if not is_injured and has_injury:
                        return False
                return True
            
            # Filter in memory
            filtered = [p for p in query if matches(p)]
            query = InMemoryQueryResult(filtered)
        
        return query
    
    def _apply_player_sort(
        self,
        query: QueryResult,
        sort: Optional[str] = None
    ) -> QueryResult:
        """Apply player sorting in a repo-agnostic way."""
        
        if hasattr(query, 'order_by'):
            # Use DB-native sorting
            from ..db_engine import Player
            
            if sort == "cost-asc":
                query = query.order_by(Player.wara)
            elif sort == "cost-desc":
                query = query.order_by(-Player.wara)
            elif sort == "name-asc":
                query = query.order_by(Player.name)
            elif sort == "name-desc":
                query = query.order_by(-Player.name)
            else:
                query = query.order_by(Player.id)
        else:
            # Use Python sorting for mocks
            def get_sort_key(player):
                name = player.get('name', '')
                wara = player.get('wara', 0)
                player_id = player.get('id', 0)
                
                if sort == "cost-asc":
                    return (wara, name, player_id)
                elif sort == "cost-desc":
                    return (-wara, name, player_id)
                elif sort == "name-asc":
                    return (name, wara, player_id)
                elif sort == "name-desc":
                    return (-len(name), name, wara, player_id)  # reversed
                else:
                    return (player_id,)
            
            sorted_list = sorted(query, key=get_sort_key)
            query = InMemoryQueryResult(sorted_list)
        
        return query
    
    def _query_to_player_dicts(
        self,
        query: QueryResult,
        short_output: bool = False
    ) -> List[Dict[str, Any]]:
        """Convert query results to list of player dicts."""
        
        # Check if we have DB models or dicts
        first_item = None
        for item in query:
            first_item = item
            break
        
        if first_item is None:
            return []
        
        # If items are already dicts (from mock)
        if isinstance(first_item, dict):
            players_data = list(query)
            if short_output:
                return players_data
            # Add computed fields if needed
            return players_data
        
        # If items are DB models (from real repo)
        from ..db_engine import Player
        from playhouse.shortcuts import model_to_dict
        
        players_data = []
        for player in query:
            player_dict = model_to_dict(player, recurse=not short_output)
            players_data.append(player_dict)
        
        return players_data
    
    def search_players(
        self,
        query_str: str,
        season: Optional[int] = None,
        limit: int = 10,
        short_output: bool = False
    ) -> Dict[str, Any]:
        """
        Search players by name with fuzzy matching.
        
        Args:
            query_str: Search query
            season: Season to search (None/0 for all)
            limit: Maximum results
            short_output: Exclude related data
            
        Returns:
            Dict with count and matching players
        """
        try:
            query_lower = query_str.lower()
            search_all_seasons = season is None or season == 0
            
            # Get all players from repo
            if search_all_seasons:
                all_players = list(self.player_repo.select_season(0))
            else:
                all_players = list(self.player_repo.select_season(season))
            
            # Convert to dicts if needed
            all_player_dicts = self._query_to_player_dicts(
                InMemoryQueryResult(all_players), 
                short_output=True
            )
            
            # Sort by relevance (exact matches first)
            exact_matches = []
            partial_matches = []
            
            for player in all_player_dicts:
                name_lower = player.get('name', '').lower()
                
                if name_lower == query_lower:
                    exact_matches.append(player)
                elif query_lower in name_lower:
                    partial_matches.append(player)
            
            # Sort by season within each group (newest first)
            if search_all_seasons:
                exact_matches.sort(key=lambda p: p.get('season', 0), reverse=True)
                partial_matches.sort(key=lambda p: p.get('season', 0), reverse=True)
            
            # Combine and limit
            results = (exact_matches + partial_matches)[:limit]
            
            return {
                "count": len(results),
                "total_matches": len(exact_matches + partial_matches),
                "all_seasons": search_all_seasons,
                "players": results
            }
            
        except Exception as e:
            self.handle_error(f"Error searching players: {e}", e)
        finally:
            self.close_db()
    
    def get_player(self, player_id: int, short_output: bool = False) -> Optional[Dict[str, Any]]:
        """Get a single player by ID."""
        try:
            player = self.player_repo.get_by_id(player_id)
            if player:
                return self._player_to_dict(player, recurse=not short_output)
            return None
        except Exception as e:
            self.handle_error(f"Error fetching player {player_id}: {e}", e)
        finally:
            self.close_db()
    
    def _player_to_dict(self, player, recurse: bool = True) -> Dict[str, Any]:
        """Convert player to dict."""
        from playhouse.shortcuts import model_to_dict
        from ..db_engine import Player
        
        if isinstance(player, dict):
            return player
        return model_to_dict(player, recurse=recurse)
    
    def update_player(self, player_id: int, data: Dict[str, Any], token: str) -> Dict[str, Any]:
        """Update a player (full update via PUT)."""
        self.require_auth(token)
        
        try:
            from fastapi import HTTPException
            
            # Verify player exists
            if not self.player_repo.get_by_id(player_id):
                raise HTTPException(status_code=404, detail=f"Player ID {player_id} not found")
            
            # Execute update
            self.player_repo.update(data, player_id=player_id)
            
            return self.get_player(player_id)
            
        except Exception as e:
            self.handle_error(f"Error updating player {player_id}: {e}", e)
        finally:
            self.invalidate_related_cache(self.cache_patterns)
            self.close_db()
    
    def patch_player(self, player_id: int, data: Dict[str, Any], token: str) -> Dict[str, Any]:
        """Patch a player (partial update)."""
        self.require_auth(token)
        
        try:
            from fastapi import HTTPException
            
            player = self.player_repo.get_by_id(player_id)
            if not player:
                raise HTTPException(status_code=404, detail=f"Player ID {player_id} not found")
            
            # Apply updates using repo
            self.player_repo.update(data, player_id=player_id)
            
            return self.get_player(player_id)
            
        except Exception as e:
            self.handle_error(f"Error patching player {player_id}: {e}", e)
        finally:
            self.invalidate_related_cache(self.cache_patterns)
            self.close_db()
    
    def create_players(self, players_data: List[Dict[str, Any]], token: str) -> Dict[str, Any]:
        """Create multiple players."""
        self.require_auth(token)
        
        try:
            from fastapi import HTTPException
            
            # Check for duplicates using repo
            for player in players_data:
                dupe = self.player_repo.get_or_none(
                    season=player.get("season"),
                    name=player.get("name")
                )
                if dupe:
                    raise HTTPException(
                        status_code=500,
                        detail=f"Player {player.get('name')} already exists in Season {player.get('season')}"
                    )
            
            # Insert in batches
            self.player_repo.insert_many(players_data)
            
            return {"message": f"Inserted {len(players_data)} players"}
            
        except Exception as e:
            self.handle_error(f"Error creating players: {e}", e)
        finally:
            self.invalidate_related_cache(self.cache_patterns)
            self.close_db()
    
    def delete_player(self, player_id: int, token: str) -> Dict[str, str]:
        """Delete a player."""
        self.require_auth(token)
        
        try:
            from fastapi import HTTPException
            
            if not self.player_repo.get_by_id(player_id):
                raise HTTPException(status_code=404, detail=f"Player ID {player_id} not found")
            
            self.player_repo.delete_by_id(player_id)
            
            return {"message": f"Player {player_id} deleted"}
            
        except Exception as e:
            self.handle_error(f"Error deleting player {player_id}: {e}", e)
        finally:
            self.invalidate_related_cache(self.cache_patterns)
            self.close_db()
    
    def _format_player_csv(self, players: List[Dict]) -> str:
        """Format player list as CSV."""
        from ..db_engine import query_to_csv
        from ..db_engine import Player
        
        # Get player IDs from the list
        player_ids = [p.get('id') for p in players if p.get('id')]
        
        if not player_ids:
            # Return empty CSV with headers
            return ""
        
        # Query for CSV formatting
        query = Player.select().where(Player.id << player_ids)
        return query_to_csv(query, exclude=[Player.division_legacy, Player.mascot, Player.gsheet])


class InMemoryQueryResult:
    """
    In-memory query result for mock repositories.
    Supports filtering, sorting, and iteration.
    """
    
    def __init__(self, items: List[Dict[str, Any]]):
        self._items = list(items)
    
    def where(self, *conditions) -> 'InMemoryQueryResult':
        """Apply filter conditions (no-op for compatibility)."""
        return self
    
    def order_by(self, *fields) -> 'InMemoryQueryResult':
        """Apply sort (no-op, sorting done by service)."""
        return self
    
    def count(self) -> int:
        return len(self._items)
    
    def __iter__(self):
        return iter(self._items)
    
    def __len__(self):
        return len(self._items)
    
    def __getitem__(self, index):
        return self._items[index]


class RealPlayerRepository:
    """Real database repository implementation."""
    
    def __init__(self, model_class):
        self._model = model_class
    
    def select_season(self, season: int):
        """Return query for season."""
        return self._model.select().where(self._model.season == season)
    
    def get_by_id(self, player_id: int):
        """Get player by ID."""
        return self._model.get_or_none(self._model.id == player_id)
    
    def get_or_none(self, **conditions):
        """Get player matching conditions."""
        try:
            return self._model.get_or_none(**conditions)
        except Exception:
            return None
    
    def update(self, data: Dict, player_id: int) -> int:
        """Update player."""
        from ..db_engine import Player
        return Player.update(**data).where(Player.id == player_id).execute()
    
    def insert_many(self, data: List[Dict]) -> int:
        """Insert multiple players."""
        from ..db_engine import Player
        with Player._meta.database.atomic():
            Player.insert_many(data).execute()
        return len(data)
    
    def delete_by_id(self, player_id: int) -> int:
        """Delete player by ID."""
        from ..db_engine import Player
        return Player.delete().where(Player.id == player_id).execute()
