from bs4 import Tag
from letterboxdpy.utils.utils_file import JsonFile
from letterboxdpy.utils.utils_transform import month_to_index
from fastfingertips.datetime_utils import parse_datetime
from letterboxdpy.constants.project import DOMAIN_SHORT
from letterboxdpy.constants.selectors import PageSelectors


def try_parse(value, target_type):
    """Attempt to convert the given value to the specified target type."""
    if isinstance(value, target_type):
        return value
    
    try:
        return target_type(value)
    except (ValueError, TypeError):
        return False

def extract_and_convert_shorthand(tag) -> int:
    """Extracts text from a tag and converts shorthand notation (e.g., '6.3K') to an integer."""
    if tag and tag.text:
        count_str = tag.text.strip().replace(',', '')
        if 'K' in count_str:
            count_str = float(count_str.replace('K', ''))
            count_str *= 1000
        return int(count_str)
    return 0


def parse_iso_date(iso_date_str: str) -> dict[str, int]:
    """
    Parses an ISO 8601 formatted date string.
    Example: '2025-01-01T00:00:00Z' -> {'year': 2025, 'month': 1, 'day': 1}
    """
    dt = parse_datetime(iso_date_str)
    if dt:
        return {'year': dt.year, 'month': dt.month, 'day': dt.day}
    raise ValueError(f"Error parsing ISO date format: {iso_date_str}")

def parse_written_date(written_date_str: str) -> dict[str, int]:
    """Parses a written date string (e.g., '01 Jan 2025')."""
    try:
        date_parts = written_date_str.split()
        return {
            'year': int(date_parts[2]),
            'month': month_to_index(date_parts[1]),
            'day': int(date_parts[0])
        }
    except (IndexError, ValueError) as e:
        raise ValueError(f"Error parsing written date format: {e}")

def parse_review_date(
        review_log_type: str,  # 'Rewatched', 'Watched', or 'Added'
        review_date: Tag) -> dict[str, int]:
    """Parses the review date based on log type."""
    if review_log_type == 'Added':
        return parse_iso_date(review_date.time['datetime'])
    return parse_written_date(review_date.text)

def get_meta_content(dom, property: str = None, name: str = None) -> str | None:
    """
    Extract content from meta tag by property or name attribute.

    Args:
        dom: BeautifulSoup DOM object
        property: Meta tag property attribute (e.g., 'og:title')
        name: Meta tag name attribute (e.g., 'description')

    Returns:
        Content of the meta tag or None if not found
    """
    try:
        if property:
            elem = dom.find('meta', attrs={'property': property})
        elif name:
            elem = dom.find('meta', attrs={'name': name})
        else:
            return None

        return elem.get('content') if elem else None
    except (AttributeError, KeyError):
        return None

def get_body_content(dom, attribute: str) -> str | None:
    """
    Extract attribute value from body tag.

    Args:
        dom: BeautifulSoup DOM object
        attribute: Body tag attribute name (e.g., 'data-owner', 'class')

    Returns:
        Attribute value from body tag or None if not found
    """
    try:
        body = dom.find('body')
        return body.get(attribute) if body else None
    except (AttributeError, KeyError):
        return None

def get_movie_count_from_meta(dom, default=None) -> int:
    # Instead of making a GET request to the last page to retrieve the number of movies,
    # which could slow down the program, an alternative approach is employed.
    # The meta description of the list page is retrieved to obtain the count of movies.

    movie_count = default

    try:
        meta_description = get_meta_content(dom, name='description')

        for item in meta_description.split(' '):
            if item[0].isdigit():
                movie_count = item
                for char in item:
                    if not char.isdigit():
                        movie_count = movie_count.replace(char, '')
                break

        movie_count = int(movie_count)
        if movie_count is not None:
            # print(f"Found the movie count in the meta description as {movie_count}.")
            return int(movie_count)
        else:
            # handle the case where no digit is found in the meta description
            print("Error: No digit found in the meta description.")
            return None
    except (AttributeError, TypeError) as e:
        print(f"Error while getting movie count from meta: {e}")
        return default

def get_list_last_page(dom, default=None) -> int:
    """
    Get the number of pages in the list (last page no).
    Note: Pagination link not created when 100 or fewer films in list.
    """
    try:
        # Find last page number from pagination container
        return int(dom.find('div', class_='paginate-pages').find_all("li")[-1].a.text)
    except AttributeError:  # No pagination (≤100 films)
        return 1
    except Exception as e:
        print(f'Error checking page count: {e}')
        return default or 1


def get_list_short_url(dom, domain=DOMAIN_SHORT, default=None) -> str:
    """
    Get the short URL of the list from input field.

    Args:
        dom: BeautifulSoup DOM object
        domain: Short domain to search for (default: DOMAIN_SHORT)
        default: Default value if short URL cannot be found

    Returns:
        Short URL as string or default value
    """
    try:
        # Find input field containing short URL
        input_field = dom.find('input', type='text', value=lambda x: x and domain in x)
        if input_field:
            return input_field.get('value')
        return default
    except Exception as e:
        print(f'Error obtaining short URL: {e}')
        return default

def is_list(dom) -> bool:
    """
    Checks if the current page is a valid Letterboxd list.

    Args:
        dom: BeautifulSoup DOM object

    Returns:
        bool: True if the page is a valid list, False otherwise
    """
    try:
        meta_content = get_meta_content(dom, property='og:type')
        return meta_content == 'letterboxd:list'
    except Exception as e:
        print(f"Error checking list type: {e}")
        return False


def catch_error_message(dom) -> bool | str:
    """
    Checks if the page contains an error message.
    Returns the error message as a string if found, False otherwise.
    """
    error_body = dom.find(*PageSelectors.ERROR_BODY)
    if error_body:
        error_section = dom.find(*PageSelectors.ERROR_MESSAGE)
        if error_section and error_section.p:
            err = error_section.p.get_text()
            return err.split('\n')[0].strip()
    return False

def parse_review_text(dom_element):
    """Parse review text from a DOM element, handling spoiler warnings."""
    if not dom_element:
        return "", False
        
    # Check for container-level spoiler flag
    container_spoiler = bool(dom_element.find("p", {"class": "js-spoiler-container"}))
    
    review = dom_element.find("div", {"class": ["body-text", "js-review-body", "activity-list-description"]})
    
    if not review:
        return "", container_spoiler

    # Check for inline spoiler flag (old style or specific pages)
    inline_spoiler = bool(review.find("p", {"class": ["contains-spoilers"]}))
    spoiler = container_spoiler or inline_spoiler

    review_paragraphs = review.find_all('p')
    
    # Filter out spoiler warning paragraph if it exists inside the review body
    if inline_spoiler and review_paragraphs:
        # Check if the first paragraph actually is the spoiler warning
        if "contains spoilers" in review_paragraphs[0].text.lower():
             review_paragraphs = review_paragraphs[1:]
    elif container_spoiler:
        # If container says spoiler, make sure we don't include a warning text if it leaked into p
        # Review content is typically clean in js-review-body, so no split needed usually
        pass

    review_text = '\n'.join([p.text.strip() for p in review_paragraphs if p.text.strip()])
    return review_text, spoiler

def extract_json_ld_script(dom):
    """
    Extract JSON-LD script safely from DOM.
    
    Args:
        dom: BeautifulSoup DOM object
        
    Returns:
        Parsed JSON object or None if extraction fails
        
    Example:
        >>> script_data = extract_json_ld_script(dom)
        >>> movie_rating = script_data.get('aggregateRating', {}).get('ratingValue')
    """
    try:
        script_elem = dom.find("script", type="application/ld+json")
        if not script_elem or not script_elem.text:
            return None
        
        script_text = script_elem.text.strip()
        
        # Handle comment format: /* ... */
        if '/*' in script_text and '*/' in script_text:
            try:
                script_text = script_text.split('*/')[1].split('/*')[0]
            except IndexError:
                return None
        
        return JsonFile.parse(script_text)
    except Exception:
        return None

def extract_list_id_from_url(url: str) -> str | None:
    """
    Extract list ID from a Letterboxd list URL.
    
    Args:
        url: Full URL to a Letterboxd list (e.g., 'https://letterboxd.com/user/list/name/')
        
    Returns:
        List ID as string or None if extraction fails
        
    Example:
        >>> list_id = extract_list_id_from_url('https://letterboxd.com/nmcassa/list/def-con-movie-list/')
        >>> print(list_id)  # '30052453'
    """
    from letterboxdpy.core.scraper import parse_url
    from letterboxdpy.pages.user_list import extract_list_id
    
    try:
        dom = parse_url(url)
        return extract_list_id(dom)
    except Exception as e:
        print(f"Error extracting list ID from URL: {e}")
        return None
