import re
import logging
from pathlib import Path
from collections import Counter
from ..constants import (
    SOURCE_DICT, FRAME_CLASSES, MOVIE_DB_DICT, SPECIAL_EDITIONS, SKIP_WORDS,
    NON_STANDARD_QUALITY_INDICATORS,
    is_valid_year,
    CYRILLIC_TO_ENGLISH
)
from ..cache import cached_method
from ..utils.pattern_utils import PatternExtractor
import langcodes

logger = logging.getLogger(__name__)


class FilenameExtractor:
    """Class to extract information from filename"""

    def __init__(self, file_path: Path | str):
        if isinstance(file_path, str):
            self.file_path = Path(file_path)
            self.file_name = file_path
        else:
            self.file_path = file_path
            self.file_name = file_path.name

        # Initialize utility helper
        self._pattern_extractor = PatternExtractor()

    def _normalize_cyrillic(self, text: str) -> str:
        """Normalize Cyrillic characters to English equivalents for parsing"""
        for cyr, eng in CYRILLIC_TO_ENGLISH.items():
            text = text.replace(cyr, eng)
        return text

    def _get_frame_class_from_height(self, height: int) -> str | None:
        """Get frame class from video height using FRAME_CLASSES constant"""
        for frame_class, info in FRAME_CLASSES.items():
            if height == info['nominal_height']:
                return frame_class
        return None

    @cached_method()
    def extract_title(self) -> str | None:
        """Extract movie title from filename"""
        # Find positions of year, source, and quality brackets
        year_pos = -1
        source_pos = -1
        quality_pos = -1
        paren_match = None
        dot_match = None
        
        # Find year position (either (YYYY) or .YYYY.)
        paren_match = re.search(r'\((\d{4})\)', self.file_name)
        if paren_match:
            year_pos = paren_match.start()
        else:
            dot_match = re.search(r'\.(\d{4})\.', self.file_name)
            if dot_match:
                year_pos = dot_match.start()
            else:
                # Last resort: any 4-digit number
                any_match = re.search(r'\b(\d{4})\b', self.file_name)
                if any_match:
                    year = int(any_match.group(1))
                    # Basic sanity check using constants
                    if is_valid_year(year):
                        year_pos = any_match.start()  # Cut before the year for plain years
        
        # Find source position
        source = self.extract_source()
        if source:
            for alias in SOURCE_DICT[source]:
                match = re.search(r'\b' + re.escape(alias) + r'\b', self.file_name, re.IGNORECASE)
                if match:
                    source_pos = match.start()
                    break
        
        # Find quality bracket position (like [720p,ukr,eng])
        quality_match = re.search(r'\[[^\]]*(?:720p|1080p|2160p|480p|SD|HD|HDR)[^\]]*\]', self.file_name)
        if quality_match:
            quality_pos = quality_match.start()
        
        # Find the earliest position that's not at the beginning
        positions = [pos for pos in [year_pos, source_pos, quality_pos] if pos > 0]
        cut_pos = min(positions) if positions else -1
        
        # Extract title (everything before the cut position)
        if cut_pos > 0:
            title = self.file_name[:cut_pos].strip()
        else:
            # No delimiters found after position 0, take everything before the last dot
            title = self.file_name.rsplit('.', 1)[0].strip()
        
        # If year is at the beginning, remove it
        if year_pos == 0:
            if paren_match and paren_match.start() == 0:
                title = re.sub(r'^\(\d{4}\)\s*', '', title)
            elif dot_match and dot_match.start() == 0:
                title = re.sub(r'^\.\d{4}\.\s*', '', title)
        
        # Remove common prefixes that are not part of the title
        # Remove bracketed prefixes like [01.1], [1], etc.
        title = re.sub(r'^\s*\[[^\]]+\]\s*', '', title)
        
        # Remove order number prefixes like 01., 1., 1.1 followed by space/underscore
        # Only remove if the number is multi-digit or has decimal (to avoid removing single digit titles)
        match = re.match(r'^\s*(\d+(?:\.\d+)?)\.(?=\s|_)', title)
        if match:
            order = match.group(1)
            if len(order) > 1 or '.' in order:
                title = re.sub(r'^\s*(\d+(?:\.\d+)?)\.(?=\s|_)', '', title)
        
        # Remove order like 1.9 where 1 is order, 9 is title
        order = self.extract_order()
        if order:
            match = re.match(r'^' + re.escape(order) + r'\.(.+)', title)
            if match:
                title = match.group(1)
        
        # Clean up any remaining leading separators
        title = title.lstrip('_ \t')
        
        # Clean up title: remove leading/trailing brackets and dots
        title = title.strip('[](). ')

        # Replace dots with spaces if they appear to be word separators
        # Only replace dots that are surrounded by letters/digits (not at edges)
        title = re.sub(r'(?<=[a-zA-Z0-9À-ÿ])\.(?=[a-zA-Z0-9À-ÿ])', ' ', title)

        # Clean up multiple spaces
        title = re.sub(r'\s+', ' ', title).strip()

        return title if title else None

    @cached_method()
    def extract_year(self) -> str | None:
        """Extract year from filename"""
        # First try to find year in parentheses (most common and reliable)
        paren_match = re.search(r'\((\d{4})\)', self.file_name)
        if paren_match:
            return paren_match.group(1)
        
        # Fallback: look for year in dots (like .1971.)
        dot_match = re.search(r'\.(\d{4})\.', self.file_name)
        if dot_match:
            return dot_match.group(1)
        
        # Last resort: any 4-digit number (but this is less reliable)
        any_match = re.search(r'\b(\d{4})\b', self.file_name)
        if any_match:
            year = int(any_match.group(1))
            # Basic sanity check using constants
            if is_valid_year(year):
                year_pos = any_match.start()
                return str(year)
        
        return None

    @cached_method()
    def extract_source(self) -> str | None:
        """Extract video source from filename"""
        temp_name = re.sub(r'\s*\(\d{4}\)\s*|\s*\d{4}\s*|\.\d{4}\.', ' ', self.file_name)

        for src, aliases in SOURCE_DICT.items():
            for alias in aliases:
                if alias.upper() in temp_name.upper():
                    return src
        return None

    @cached_method()
    def extract_order(self) -> str | None:
        """Extract collection order number from filename (at the beginning)"""
        # Look for order patterns at the start of filename
        # Patterns: [01], [01.1], 01., 1., 1.1 followed by space or underscore
        
        # Check for bracketed patterns: [01], [01.1], etc.
        bracket_match = re.match(r'^\[(\d+(?:\.\d+)?)\]', self.file_name)
        if bracket_match:
            return bracket_match.group(1)
        
        # Check for dot patterns: 01., 1., 1.1 followed by title before (
        dot_match = re.match(r'^(\d+(?:\.\d)*)\.?\s*', self.file_name)
        if dot_match and '.' in dot_match.group(0):
            order = dot_match.group(1)
            if '.' in order:
                parts = order.split('.')
                if len(parts) > 1 and parts[-1] != '1':
                    order = parts[0]
            return order
        
        return None

    @cached_method()
    def extract_frame_class(self) -> str | None:
        """Extract frame class from filename (480p, 720p, 1080p, 2160p, etc.)"""
        # Normalize Cyrillic characters for resolution parsing
        normalized_name = self._normalize_cyrillic(self.file_name)
        
        # First check for specific numeric resolutions with p/i
        match = re.search(r'(\d{3,4})([pi])', normalized_name, re.IGNORECASE)
        if match:
            height = int(match.group(1))
            scan_type = match.group(2).lower()
            frame_class = f"{height}{scan_type}"
            if frame_class in FRAME_CLASSES:
                return frame_class
            # Fallback to height-based if not in constants
            return self._get_frame_class_from_height(height)
        
        # If no specific resolution found, check for non-standard quality indicators
        for indicator in NON_STANDARD_QUALITY_INDICATORS:
            if re.search(r'\b' + re.escape(indicator) + r'\b', self.file_name, re.IGNORECASE):
                return None
        
        return None

    @cached_method()
    def extract_hdr(self) -> str | None:
        """Extract HDR information from filename"""
        # Check for SDR first - indicates no HDR
        if re.search(r'\bSDR\b', self.file_name, re.IGNORECASE):
            return None
        
        # Check for HDR, but not NoHDR
        if re.search(r'\bHDR\b', self.file_name, re.IGNORECASE) and not re.search(r'\bNoHDR\b', self.file_name, re.IGNORECASE):
            return 'HDR'
        
        return None

    @cached_method()
    def extract_movie_db(self) -> list[str] | None:
        """Extract movie database identifier from filename"""
        # Use PatternExtractor utility to avoid code duplication
        db_info = self._pattern_extractor.extract_movie_db_ids(self.file_name)
        if db_info:
            return [db_info['type'], db_info['id']]
        return None

    @cached_method()
    def extract_special_info(self) -> list[str] | None:
        """Extract special edition information from filename"""
        # Look for special edition indicators in brackets or as standalone text
        special_info = []
        
        for canonical_edition, variants in SPECIAL_EDITIONS.items():
            for edition in variants:
                # Check in brackets: [Theatrical Cut], [Director's Cut], etc.
                bracket_pattern = r'\[([^\]]+)\]'
                brackets = re.findall(bracket_pattern, self.file_name)
                for bracket in brackets:
                    # Check if bracket contains comma-separated items
                    items = [item.strip() for item in bracket.split(',')]
                    for item in items:
                        if edition.lower() == item.lower().strip():
                            if canonical_edition not in special_info:
                                special_info.append(canonical_edition)
                
                # Check as standalone text (case-insensitive)
                if re.search(r'\b' + re.escape(edition) + r'\b', self.file_name, re.IGNORECASE):
                    if canonical_edition not in special_info:
                        special_info.append(canonical_edition)
        
        return special_info if special_info else None

    @cached_method()
    def extract_audio_langs(self) -> str:
        """Extract audio languages from filename"""
        # Look for language patterns in brackets and outside brackets
        # Skip subtitle indicators and focus on audio languages
        
        langs = []
        
        # First, look for languages inside brackets
        bracket_pattern = r'\[([^\]]+)\]'
        brackets = re.findall(bracket_pattern, self.file_name)
        
        for bracket in brackets:
            bracket_lower = bracket.lower()
            
            # Skip brackets that contain movie database patterns
            if any(db in bracket_lower for db in ['imdb', 'tmdb', 'tvdb']):
                continue
            
            # Parse items separated by commas or underscores
            items = re.split(r'[,_]', bracket)
            items = [item.strip() for item in items]
            
            for item in items:
                # Skip empty items or items that are clearly not languages
                if not item or len(item) < 2:
                    continue
                
                item_lower = item.lower()
                
                # Skip subtitle indicators
                if item_lower in ['sub', 'subs', 'subtitle']:
                    continue
                
                # Check if item contains language codes (2-3 letter codes)
                # Pattern: optional number + optional 'x' + language code
                # Allow the language code to be at the end of the item
                lang_match = re.search(r'(?:(\d+)x?)?([a-z]{2,3})$', item_lower)
                if lang_match:
                    count = int(lang_match.group(1)) if lang_match.group(1) else 1
                    lang_code = lang_match.group(2)
                    
                    # Skip if it's a quality/resolution indicator or other skip word
                    if lang_code in SKIP_WORDS:
                        continue
                    
                    # Skip if the language code is not at the end or if there are extra letters after
                    # But allow prefixes like numbers and 'x'
                    prefix = item_lower[:-len(lang_code)]
                    if not re.match(r'^(?:\d+x?)?$', prefix):
                        continue
                    
                    # Convert to 3-letter ISO code
                    try:
                        lang_obj = langcodes.Language.get(lang_code)
                        iso3_code = lang_obj.to_alpha3()
                        langs.extend([iso3_code] * count)
                    except (LookupError, ValueError, AttributeError) as e:
                        # Skip invalid language codes
                        logger.debug(f"Invalid language code '{lang_code}': {e}")
                        pass
        
        # Second, look for standalone language codes outside brackets
        # Remove bracketed content first
        text_without_brackets = re.sub(r'\[([^\]]+)\]', '', self.file_name)

        # Split on dots, spaces, and underscores
        parts = re.split(r'[.\s_]+', text_without_brackets)

        for part in parts:
            part = part.strip()
            if not part or len(part) < 2:
                continue

            part_lower = part.lower()

            # Check if this part is a 2-3 letter code
            if not re.match(r'^[a-zA-Z]{2,3}$', part):
                continue

            # Skip title case 2-letter words to avoid false positives like "In" -> "ind"
            if part.istitle() and len(part) == 2:
                continue

            # Skip known non-language words
            if part_lower in SKIP_WORDS:
                continue

            # Try to validate with langcodes library
            try:
                lang_obj = langcodes.Language.get(part_lower)
                iso3_code = lang_obj.to_alpha3()
                langs.append(iso3_code)
            except (LookupError, ValueError, AttributeError) as e:
                # Not a valid language code, skip
                logger.debug(f"Invalid language code '{part_lower}': {e}")
                pass
        
        if not langs:
            return ''
            
        # Count occurrences while preserving order of first appearance
        lang_counts = {}
        for lang in langs:
            if lang not in lang_counts:
                lang_counts[lang] = 0
            lang_counts[lang] += 1
        
        # Format like mediainfo: "2ukr,eng" preserving order
        audio_langs = [f"{count}{lang}" if count > 1 else lang for lang, count in lang_counts.items()]
        return ','.join(audio_langs)

    @cached_method()
    def extract_extension(self) -> str | None:
        """Extract file extension from filename"""
        # Use pathlib to extract extension properly
        ext = self.file_path.suffix
        # Remove leading dot and return
        return ext[1:] if ext else None

    @cached_method()
    def extract_audio_tracks(self) -> list[dict]:
        """Extract audio track data from filename (simplified version with only language)"""
        # Similar to extract_audio_langs but returns list of dicts

        tracks = []

        # First, look for languages inside brackets
        bracket_pattern = r'\[([^\]]+)\]'
        brackets = re.findall(bracket_pattern, self.file_name)

        for bracket in brackets:
            bracket_lower = bracket.lower()

            # Skip brackets that contain movie database patterns
            if any(db in bracket_lower for db in ['imdb', 'tmdb', 'tvdb']):
                continue

            # Parse items separated by commas or underscores
            items = re.split(r'[,_]', bracket)
            items = [item.strip() for item in items]

            for item in items:
                # Skip empty items or items that are clearly not languages
                if not item or len(item) < 2:
                    continue

                item_lower = item.lower()

                # Skip subtitle indicators
                if item_lower in ['sub', 'subs', 'subtitle']:
                    continue

                # Check if item contains language codes (2-3 letter codes)
                # Pattern: optional number + optional 'x' + language code
                # Allow the language code to be at the end of the item
                lang_match = re.search(r'(?:(\d+)x?)?([a-z]{2,3})$', item_lower)
                if lang_match:
                    count = int(lang_match.group(1)) if lang_match.group(1) else 1
                    lang_code = lang_match.group(2)

                    # Skip if it's a quality/resolution indicator or other skip word
                    if lang_code in SKIP_WORDS:
                        continue

                    # Skip if the language code is not at the end or if there are extra letters after
                    # But allow prefixes like numbers and 'x'
                    prefix = item_lower[:-len(lang_code)]
                    if not re.match(r'^(?:\d+x?)?$', prefix):
                        continue

                    # Convert to 3-letter ISO code
                    try:
                        lang_obj = langcodes.Language.get(lang_code)
                        iso3_code = lang_obj.to_alpha3()
                        tracks.append({'language': iso3_code})
                    except (LookupError, ValueError, AttributeError) as e:
                        # Skip invalid language codes
                        logger.debug(f"Invalid language code '{lang_code}': {e}")
                        pass

        # Second, look for standalone language codes outside brackets
        # Remove bracketed content first
        text_without_brackets = re.sub(r'\[([^\]]+)\]', '', self.file_name)

        # Split on dots, spaces, and underscores
        parts = re.split(r'[.\s_]+', text_without_brackets)

        for part in parts:
            part = part.strip()
            if not part or len(part) < 2:
                continue

            part_lower = part.lower()

            # Check if this part is a 2-3 letter code
            if not re.match(r'^[a-zA-Z]{2,3}$', part):
                continue

            # Skip title case 2-letter words to avoid false positives like "In" -> "ind"
            if part.istitle() and len(part) == 2:
                continue

            # Skip known non-language words
            if part_lower in SKIP_WORDS:
                continue

            # Try to validate with langcodes library
            try:
                lang_obj = langcodes.Language.get(part_lower)
                iso3_code = lang_obj.to_alpha3()
                tracks.append({'language': iso3_code})
            except (LookupError, ValueError, AttributeError) as e:
                # Not a valid language code, skip
                logger.debug(f"Invalid language code '{part_lower}': {e}")
                pass

        return tracks