"""File content resolver for CLI options.

Provides support for reading content from files using the @file syntax,
similar to curl and httpie. This allows users to pass file contents to
CLI options without using shell command substitution.

Syntax:
    @filepath   -> reads content from filepath
    @-          -> reads content from stdin
    text        -> literal text (no @ prefix)
    @@text      -> escaped @ (literal @text)

Examples:
    --system @prompt.txt           -> reads content from prompt.txt
    --system @-                    -> reads content from stdin
    --system "You are helpful"     -> literal text
    --system @@handle              -> literal "@handle" (escaped)

Usage:
    from cli.infrastructure.file_content import resolve_content

    # In command function
    system_instruction = resolve_content(system_instruction)

    # Or use the typer callback for automatic resolution
    from cli.infrastructure.file_content import file_content_callback

    @app.command()
    def my_command(
        system: Annotated[
            str | None,
            typer.Option("--system", "-s", callback=file_content_callback),
        ] = None,
    ) -> None:
        # system is already resolved (file content or literal)
        ...
"""

import sys
from pathlib import Path


class FileContentError(Exception):
    """Raised when file content cannot be read."""

    pass


def resolve_content(value: str | None) -> str | None:
    """Resolve a value that may be a file reference or literal text.

    Supports the @file syntax for reading content from files:
    - @filepath: reads content from the specified file
    - @-: reads content from stdin
    - @@text: escaped @ (returns literal @text)
    - other: returned as-is (literal text)

    Args:
        value: The value to resolve. May be a file reference (@file),
               stdin reference (@-), escaped @ (@@), or literal text.

    Returns:
        The resolved content, or None if input was None.

    Raises:
        FileContentError: If the file cannot be read or doesn't exist.

    Examples:
        >>> resolve_content("@prompt.txt")  # reads file
        "You are a helpful assistant..."

        >>> resolve_content("@-")  # reads stdin
        "Content from stdin..."

        >>> resolve_content("@@username")  # escaped
        "@username"

        >>> resolve_content("literal text")  # passthrough
        "literal text"

        >>> resolve_content(None)
        None
    """
    if value is None:
        return None

    # Not a file reference - return as-is
    if not value.startswith("@"):
        return value

    # Escaped @ - return without the escape character
    if value.startswith("@@"):
        return value[1:]

    # Extract the path (everything after @)
    path_str = value[1:]

    # Handle stdin
    if path_str == "-":
        return _read_stdin()

    # Handle file path
    return _read_file(path_str)


def _read_stdin() -> str:
    """Read content from stdin.

    Returns:
        Content read from stdin.

    Raises:
        FileContentError: If stdin cannot be read or is a TTY with no input.
    """
    try:
        # Check if stdin is a TTY (interactive terminal with no piped input)
        if sys.stdin.isatty():
            raise FileContentError(
                "No input provided on stdin. "
                "Use @- with piped input: echo 'content' | alloy ... -s @-"
            )
        return sys.stdin.read()
    except FileContentError:
        raise
    except Exception as e:
        raise FileContentError(f"Failed to read from stdin: {e}")


def _read_file(path_str: str) -> str:
    """Read content from a file.

    Args:
        path_str: Path to the file to read.

    Returns:
        Content of the file.

    Raises:
        FileContentError: If the file doesn't exist or cannot be read.
    """
    path = Path(path_str).expanduser()

    if not path.exists():
        raise FileContentError(f"File not found: {path_str}")

    if not path.is_file():
        raise FileContentError(f"Not a file: {path_str}")

    try:
        return path.read_text(encoding="utf-8")
    except PermissionError:
        raise FileContentError(f"Permission denied: {path_str}")
    except UnicodeDecodeError:
        raise FileContentError(
            f"File is not valid UTF-8 text: {path_str}. Only text files are supported."
        )
    except Exception as e:
        raise FileContentError(f"Failed to read file '{path_str}': {e}")


def resolve_content_or_raise(value: str | None) -> str | None:
    """Resolve content, converting FileContentError to typer.BadParameter.

    This is a convenience wrapper for use in command functions where you want
    file errors to be displayed as CLI parameter errors.

    Args:
        value: The value to resolve.

    Returns:
        The resolved content, or None if input was None.

    Raises:
        typer.BadParameter: If the file cannot be read.
    """
    import typer

    try:
        return resolve_content(value)
    except FileContentError as e:
        raise typer.BadParameter(str(e))


def file_content_callback(value: str | None) -> str | None:
    """Typer callback that resolves file content references.

    Use this as a callback for typer.Option to automatically resolve
    @file references before the value reaches your command function.

    Args:
        value: The option value, possibly a file reference.

    Returns:
        The resolved content.

    Raises:
        typer.BadParameter: If the file cannot be read.

    Example:
        @app.command()
        def cmd(
            system: Annotated[
                str | None,
                typer.Option("--system", "-s", callback=file_content_callback),
            ] = None,
        ):
            # system contains file content if @file was used
            print(system)
    """
    return resolve_content_or_raise(value)
