import os
import re

from actions_logging.app_logging import logger
from common.common import raise_with_context
from github.github_apis import get_team_members


def find_codeowners_file(repo_path: str) -> str:
    """
    Find the codeowners file in the repository.

    :param repo_path: The path to the repository.
    :return: The path to the codeowners file.
    """

    try:
        codeowners_file_paths = [
            os.path.join(repo_path, '.github/CODEOWNERS'),
            os.path.join(repo_path, 'CODEOWNERS'),
            os.path.join(repo_path, 'docs/CODEOWNERS'),
        ]
        logger.debug(f"Checking for codeowners file at paths: {codeowners_file_paths}")
        for codeowners_file_path in codeowners_file_paths:
            if os.path.exists(codeowners_file_path):
                logger.debug(f"Found codeowners file at {codeowners_file_path}")
                return codeowners_file_path
        logger.warning(f"Neither codeowner file path exist ({codeowners_file_paths}).")
        return ''
    except Exception as e:
        raise_with_context(e, RuntimeError, "Error finding codeowners file")

def parse_codeowners(codeowners: str, resolve_team_members: bool = False) -> dict[str, list[str]]:
    """
    Parse the codeowners from a CODEOWNERS file.

    :param codeowners: The CODEOWNERS file content.
    :param resolve_team_members: Whether to resolve team members or not.
    :return: A dictionary with the list of code owner users and teams. If resolve_team_members is True, the list of teams will be empty and the list of users will contain the members of the teams.
    """

    try:
        parsed_codeowners = {
            'users': [],
            'teams': []
        }

        parsed_handles = [codeowner.strip() for codeowner in re.findall(r'^@[\w/_-]+|\s@[\w/_-]+', codeowners)] # find all github handles (starting with @ in the beginning of the line or after a space)
        for handle in parsed_handles:
            if '/' in handle:
                logger.debug(f"Identified a team handle (contains a slash): {handle}")
                parsed_codeowners['teams'].append(handle.strip('@'))
            else:
                logger.debug(f"Identified a user handle (does not contain a slash): {handle}")
                parsed_codeowners['users'].append(handle.strip('@'))

        if resolve_team_members:
            logger.debug(f"Resolving team members for teams: {parsed_codeowners['teams']}")
            for handle in parsed_codeowners['teams']:
                try:
                    logger.debug(f"Getting team members for team {handle}")
                    for member in get_team_members(handle.split('/')[1]):
                        parsed_codeowners['users'].append(member['login'])
                except Exception as e:
                    logger.error(f"Error getting team members for team {handle}: {e}")
            logger.debug(f"Removing teams from return value as resolve_team_members is set to True")
            parsed_codeowners['teams'] = []

        # sort the parsed_codeowners['users'] and parsed_codeowners['teams'] lists and remove duplicates
        parsed_codeowners['users'] = sorted(list(set(parsed_codeowners['users'])))
        parsed_codeowners['teams'] = sorted(list(set(parsed_codeowners['teams'])))

        return parsed_codeowners
    except Exception as e:
        raise_with_context(e, RuntimeError, "Error parsing codeowners file")

def get_codeowners_in_repo_path(repo_path: str, resolve_team_members: bool = False, codeowners_file_required: bool = False, return_empty_list_on_exceptions: bool = True) -> dict[str, list[str]]:
    """
    Get the code owners of the repository.

    :param repo_path: The path to the repository.
    :param resolve_team_members: Whether to resolve team members or not.
    :param codeowners_file_required: Whether the codeowners file is required or not. If set to True, an exception will be raised if the codeowners file is not found.
    :param return_empty_list_on_exceptions: Whether to return an empty list on exceptions or not. If set to False, the exception will be raised.
    :return: A dictionary with the list of code owner users and teams. If resolve_team_members is True, the list of teams will be empty and the list of users will contain the members of the teams.
    """

    try:
        logger.debug(f"Getting code owners in repo path: {repo_path}")
        codeowners_file_path = find_codeowners_file(repo_path)
        logger.debug(f"Codeowners file path: {codeowners_file_path}")
        if codeowners_file_path:
            with open(codeowners_file_path, 'r') as codeowners_file:
                codeowners = codeowners_file.read()
                logger.debug(f"Codeowners file content: {codeowners}")
                codeowners_parsed = parse_codeowners(codeowners, resolve_team_members)
                logger.debug(f"All codeowners parsed from file: {codeowners_parsed}")
                return codeowners_parsed
        elif not codeowners_file_required:
            logger.warning(f"Codeowners file not found at {repo_path}. Returning empty dict as codeowners_file_required is set to False.")
            return {}
        else:
            raise_with_context(None, RuntimeError, "Codeowners file not found")
    except Exception as e:
        if return_empty_list_on_exceptions:
            logger.error(f"Returning empty list on exceptions: {e}")
            return {}
        else:
            raise_with_context(e, RuntimeError, "Error getting code owners of the repository")
