import os
from pathlib import Path
from datetime import datetime
from fuzzywuzzy import fuzz


class ElevenLabsMcpError(Exception):
    def __init__(self, message: str, code: str = None, suggestion: str = None):
        self.message = message
        self.code = code
        self.suggestion = suggestion
        
        # Build the full error message
        full_message = message
        if code:
            full_message = f"[{code}] {full_message}"
        if suggestion:
            full_message += f"\nSuggestion: {suggestion}"
            
        super().__init__(full_message)


def make_error(error_text: str, code: str = None, suggestion: str = None):
    raise ElevenLabsMcpError(error_text, code, suggestion)


def is_file_writeable(path: Path) -> bool:
    if path.exists():
        return os.access(path, os.W_OK)
    parent_dir = path.parent
    return os.access(parent_dir, os.W_OK)


def make_output_file(
    tool: str, text: str, output_path: Path, extension: str, full_id: bool = False
) -> Path:
    id = text if full_id else text[:5]

    output_file_name = f"{tool}_{id.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{extension}"
    return output_path / output_file_name


def make_output_path(
    output_directory: str | None, base_path: str | None = None
) -> Path:
    output_path = None
    if output_directory is None:
        output_path = Path.home() / "Desktop"
    elif not os.path.isabs(output_directory) and base_path:
        output_path = Path(os.path.expanduser(base_path)) / Path(output_directory)
    else:
        output_path = Path(os.path.expanduser(output_directory))
    if not is_file_writeable(output_path):
        make_error(
            f"Directory ({output_path}) is not writeable",
            code="DIRECTORY_NOT_WRITEABLE",
            suggestion="Check directory permissions or use a different output directory"
        )
    output_path.mkdir(parents=True, exist_ok=True)
    return output_path


def find_similar_filenames(
    target_file: str, directory: Path, threshold: int = 70
) -> list[tuple[str, int]]:
    """
    Find files with names similar to the target file using fuzzy matching.

    Args:
        target_file (str): The reference filename to compare against
        directory (str): Directory to search in (defaults to current directory)
        threshold (int): Similarity threshold (0 to 100, where 100 is identical)

    Returns:
        list: List of similar filenames with their similarity scores
    """
    target_filename = os.path.basename(target_file)
    similar_files = []
    for root, _, files in os.walk(directory):
        for filename in files:
            if (
                filename == target_filename
                and os.path.join(root, filename) == target_file
            ):
                continue
            similarity = fuzz.token_sort_ratio(target_filename, filename)

            if similarity >= threshold:
                file_path = Path(root) / filename
                similar_files.append((file_path, similarity))

    similar_files.sort(key=lambda x: x[1], reverse=True)

    return similar_files


def try_find_similar_files(
    filename: str, directory: Path, take_n: int = 5
) -> list[Path]:
    similar_files = find_similar_filenames(filename, directory)
    if not similar_files:
        return []

    filtered_files = []

    for path, _ in similar_files[:take_n]:
        if check_audio_file(path):
            filtered_files.append(path)

    return filtered_files


def check_audio_file(path: Path) -> bool:
    audio_extensions = {
        ".wav",
        ".mp3",
        ".m4a",
        ".aac",
        ".ogg",
        ".flac",
        ".mp4",
        ".avi",
        ".mov",
        ".wmv",
    }
    return path.suffix.lower() in audio_extensions


def handle_input_file(file_path: str, audio_content_check: bool = True) -> Path:
    if not os.path.isabs(file_path) and not os.environ.get("ELEVENLABS_MCP_BASE_PATH"):
        make_error(
            "File path must be an absolute path if ELEVENLABS_MCP_BASE_PATH is not set",
            code="RELATIVE_PATH_ERROR",
            suggestion="Use an absolute path starting with / (Unix) or C:\\ (Windows), or set ELEVENLABS_MCP_BASE_PATH environment variable"
        )
    path = Path(file_path)
    if not path.exists() and path.parent.exists():
        parent_directory = path.parent
        similar_files = try_find_similar_files(path.name, parent_directory)
        similar_files_formatted = ",".join([str(file) for file in similar_files])
        if similar_files:
            make_error(
                f"File ({path}) does not exist",
                code="FILE_NOT_FOUND",
                suggestion=f"Did you mean any of these files: {similar_files_formatted}?"
            )
        make_error(
            f"File ({path}) does not exist",
            code="FILE_NOT_FOUND",
            suggestion="Check the file path and ensure the file exists"
        )
    elif not path.exists():
        make_error(
            f"File ({path}) does not exist",
            code="FILE_NOT_FOUND",
            suggestion="Check the file path and ensure the file exists"
        )
    elif not path.is_file():
        make_error(
            f"Path ({path}) is not a file",
            code="NOT_A_FILE",
            suggestion="Ensure the path points to a file, not a directory"
        )

    if audio_content_check and not check_audio_file(path):
        make_error(
            f"File ({path}) is not an audio or video file",
            code="INVALID_FILE_TYPE",
            suggestion="Use a supported audio format: wav, mp3, m4a, aac, ogg, flac, mp4, avi, mov, wmv"
        )
    return path
