from PIL import Image, ImageDraw, ImageFont
import os
from typing import Optional, List, Union
from docassemble.base.util import log


def find_font_file_by_name(font_name: str, search_dirs: List[str]) -> Optional[str]:
    """
    Recursively search for a font file by name in the specified directories.

    This function appends '.ttf' to the font name if it is not already present and
    searches through each directory in `search_dirs` recursively for a file that
    matches the font name (case-insensitive).

    Args:
        font_name (str): The name of the font to search for (without path).
        search_dirs (List[str]): A list of directories to search.

    Returns:
        Optional[str]: The full path to the font file if found; otherwise, None.
    """
    # Append '.ttf' if necessary.
    if not font_name.lower().endswith(".ttf"):
        target_font = font_name + ".ttf"
    else:
        target_font = font_name

    for directory in search_dirs:
        if os.path.exists(directory):
            for root, _, files in os.walk(directory):
                for file in files:
                    if file.lower() == target_font.lower():
                        return os.path.join(root, file)
    return None


def get_font(
    font_name: Optional[str] = None, font_size: int = 48
) -> Union[ImageFont.ImageFont, ImageFont.FreeTypeFont]:
    """
    Loads a font by name from candidate directories and returns an ImageFont instance.

    If `font_name` is provided, it may be either a full path to a font file or a font
    name (without a path). In the latter case, the function will search for the font in
    `/var/www/.fonts` and `/usr/share/fonts/truetype/`, automatically appending '.ttf'
    if necessary. If no font is found, the function falls back to the default Pillow font.

    Args:
        font_name (Optional[str]): The desired font's full path or name. Defaults to None.
        font_size (int): The size of the font to be used. Defaults to 48.

    Returns:
        ImageFont.ImageFont: The loaded font instance.
    """
    search_dirs: List[str] = ["/var/www/.fonts", "/usr/share/fonts/truetype"]

    if font_name:
        # If a full path is provided and exists, use it.
        if os.path.isabs(font_name) and os.path.exists(font_name):
            try:
                return ImageFont.truetype(font_name, font_size)
            except Exception as e:
                print(f"Error loading provided font {font_name}: {e}")
        else:
            # Try to find the font by name in the candidate directories.
            found_font = find_font_file_by_name(font_name, search_dirs)
            if found_font:
                try:
                    return ImageFont.truetype(found_font, font_size)
                except Exception as e:
                    log(f"Error loading font {found_font} found for {font_name}: {e}")
            else:
                log(f"Font '{font_name}' not found in candidate directories.")
    else:
        # No font specified; try default candidate font names.
        candidate_font_names: List[str] = [
            "BadScript-Regular",
            "arial",
            "times",
            "DejaVuSans",
        ]
        for candidate in candidate_font_names:
            found_font = find_font_file_by_name(candidate, search_dirs)
            if found_font:
                try:
                    return ImageFont.truetype(found_font, font_size)
                except Exception as e:
                    log(f"Error loading candidate font {found_font}: {e}")

    # Final fallback to Pillow's default font.
    log("Falling back to the default Pillow font.")
    return ImageFont.load_default()


def create_signature(
    name: str,
    output_file: str,
    signature_prefix: str = "/s/",
    font_name: Optional[str] = None,
    font_size: int = 48,
) -> None:
    """
    Creates an image file that simulates a signature.

    The signature image is generated by combining the provided `signature_prefix`
    (for example, "/s/" or "s/") with the person's name. The text size is measured to
    create an image with appropriate dimensions, and then the text is drawn onto a
    white background. The final image is saved to the specified output file path.

    Args:
        name (str): The person's name to be signed.
        output_file (str): The full file path where the image will be written.
        signature_prefix (str): The signature prefix to use (e.g., "/s/" or "s/"). Defaults to "/s/".
        font_name (Optional[str]): The font's full path or name (without path). Defaults to None.
        font_size (int): The size of the font to be used. Defaults to 48.
    """
    # Construct the signature text.
    signature_text = f"{signature_prefix} {name}" if signature_prefix.strip() else name

    # Load the font using the provided font name or default candidate fonts.
    font = get_font(font_name, font_size)

    # Create a temporary image to measure text size.
    dummy_img = Image.new("RGB", (1, 1))
    dummy_draw = ImageDraw.Draw(dummy_img)

    bbox = dummy_draw.textbbox((0, 0), signature_text, font=font)
    text_width = bbox[2] - bbox[0]
    text_height = bbox[3] - bbox[1]

    # Define margins around the text.
    margin = 20
    img_width = text_width + 2 * margin
    img_height = text_height + 2 * margin

    # Create the final image with a white background.
    img = Image.new("RGB", (int(img_width), int(img_height)), "white")
    draw = ImageDraw.Draw(img)

    # Draw the signature text onto the image.
    draw.text((margin, margin), signature_text, font=font, fill="black")

    # Save the image to the specified output file path.
    img.save(output_file, format="PNG")
