"""File I/O operations for sseed application.

Provides comprehensive file operations with UTF-8 encoding, comment support,
and secure file handling for BIP39/SLIP39 operations.
"""

import datetime
import os
import sys
from pathlib import Path

from sseed.exceptions import FileError
from sseed.logging_config import get_logger
from sseed.validation import (
    normalize_input,
    sanitize_filename,
    validate_mnemonic_words,
)

logger = get_logger(__name__)


def read_mnemonic_from_file(file_path: str) -> str:
    """Read and validate a mnemonic from a file.

    Reads a mnemonic from a file, ignoring comment lines (starting with #)
    and applying input normalization as specified in PRD Section 6.

    Args:
        file_path: Path to the file containing the mnemonic.

    Returns:
        The normalized mnemonic string.

    Raises:
        FileError: If file cannot be read or contains invalid content.
        ValidationError: If mnemonic format is invalid.
    """
    try:
        file_path_obj = Path(file_path)

        if not file_path_obj.exists():
            raise FileError(f"Mnemonic file not found: {file_path}")

        logger.info("Reading mnemonic from file: %s", file_path_obj)

        if not file_path_obj.is_file():
            raise FileError(f"Path is not a file: {file_path}")

        with open(file_path_obj, encoding="utf-8") as f:
            content = f.read().strip()

        if not content:
            raise FileError(f"File is empty: {file_path}")

        # Extract mnemonic (ignoring comments)
        mnemonic_lines = []
        for line in content.split("\n"):
            line = line.strip()
            if line and not line.startswith("#"):
                mnemonic_lines.append(line)

        if not mnemonic_lines:
            raise FileError(f"No mnemonic found in file: {file_path}")

        if len(mnemonic_lines) > 1:
            raise FileError(f"File contains multiple non-comment lines: {file_path}")

        mnemonic = normalize_input(mnemonic_lines[0])

        # Validate mnemonic format
        words = mnemonic.split()
        validate_mnemonic_words(words)

        logger.info("Successfully read mnemonic from file: %s", file_path_obj)
        return mnemonic

    except OSError as e:
        error_msg = f"Failed to read mnemonic file {file_path}: {e}"
        logger.error(error_msg)
        raise FileError(error_msg) from e


def write_mnemonic_to_file(mnemonic: str, file_path: str, include_comments: bool = True) -> None:
    """Write a mnemonic to a file.

    Writes a mnemonic to a file in UTF-8 format as specified in Phase 6 requirements.

    Args:
        mnemonic: Mnemonic string to write.
        file_path: Path where to write the file.
        include_comments: Whether to include descriptive comments (default: True).

    Raises:
        FileError: If file cannot be written.
    """
    try:
        # Convert to Path and sanitize only the filename component
        file_path_obj = Path(file_path)
        safe_filename = sanitize_filename(file_path_obj.name)
        safe_path = file_path_obj.parent / safe_filename

        # Create directory if it doesn't exist
        safe_path.parent.mkdir(parents=True, exist_ok=True)

        logger.info("Writing mnemonic to file: %s", safe_path)

        # Write with UTF-8 encoding (Phase 6 requirement)
        with open(safe_path, "w", encoding="utf-8") as f:
            if include_comments:
                # Enhanced comment header (Phase 6 requirement)
                f.write("# BIP-39 Mnemonic File\n")
                f.write(
                    f"# Generated by sseed on "
                    f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
                )
                f.write("#\n")
                f.write(
                    "# This file contains a BIP-39 mnemonic for cryptocurrency wallet recovery.\n"
                )
                f.write(
                    "# Keep this file extremely secure and consider "
                    "splitting into SLIP-39 shards.\n"
                )
                f.write("# Anyone with access to this mnemonic can access your funds.\n")
                f.write("#\n")
                f.write("# File format: Plain text UTF-8\n")
                f.write("# Lines starting with '#' are comments and will be ignored.\n")
                f.write("#\n")

            # Write the actual mnemonic
            f.write(mnemonic + "\n")

        logger.info("Successfully wrote mnemonic to file: %s", safe_path)

    except Exception as e:
        error_msg = f"Failed to write mnemonic to file '{file_path}': {e}"
        logger.error(error_msg)
        raise FileError(error_msg, context={"file_path": str(file_path), "error": str(e)}) from e


def read_shard_from_file(file_path: str) -> str:
    """Read a SLIP-39 shard from a file without BIP-39 validation.

    Reads a shard from a file, ignoring comment lines (starting with #)
    and applying input normalization, but without BIP-39 mnemonic validation.

    Args:
        file_path: Path to the file containing the shard.

    Returns:
        The normalized shard string.

    Raises:
        FileError: If file cannot be read or contains invalid content.
    """
    try:
        file_path_obj = Path(file_path)

        if not file_path_obj.exists():
            raise FileError(f"Shard file not found: {file_path}")

        logger.debug("Reading shard from file: %s", file_path_obj)

        if not file_path_obj.is_file():
            raise FileError(f"Path is not a file: {file_path}")

        with open(file_path_obj, encoding="utf-8") as f:
            content = f.read().strip()

        if not content:
            raise FileError(f"File is empty: {file_path}")

        # Extract shard (ignoring comments)
        shard_lines = []
        for line in content.split("\n"):
            line = line.strip()
            if line and not line.startswith("#"):
                shard_lines.append(line)

        if not shard_lines:
            raise FileError(f"No shard found in file: {file_path}")

        if len(shard_lines) > 1:
            raise FileError(f"File contains multiple non-comment lines: {file_path}")

        shard = normalize_input(shard_lines[0])

        # Note: No BIP-39 validation for SLIP-39 shards
        logger.debug("Successfully read shard from file: %s", file_path_obj)
        return shard

    except OSError as e:
        error_msg = f"Failed to read shard file {file_path}: {e}"
        logger.error(error_msg)
        raise FileError(error_msg) from e


def read_shards_from_files(file_paths: list[str]) -> list[str]:
    """Read SLIP-39 shards from multiple files.

    Reads shards from multiple files, with each file containing one shard.
    Comment lines starting with '#' are ignored.

    Args:
        file_paths: List of file paths containing shards.

    Returns:
        List of shard strings.

    Raises:
        FileError: If any file cannot be read.
    """
    try:
        shards = []

        for file_path in file_paths:
            try:
                shard = read_shard_from_file(file_path)  # Use shard-specific reader
                shards.append(shard)
                logger.debug("Read shard from file: %s", file_path)
            except FileError as e:
                # Add context about which file failed
                raise FileError(
                    f"Failed to read shard from file '{file_path}': {e.message}",
                    context={"file_path": file_path, "original_error": str(e)},
                ) from e

        logger.info("Successfully read %d shards from files", len(shards))
        return shards

    except Exception as e:
        error_msg = f"Failed to read shards from files: {e}"
        logger.error(error_msg)
        raise FileError(error_msg, context={"file_paths": file_paths, "error": str(e)}) from e


def write_shards_to_file(shards: list[str], file_path: str) -> None:
    """Write SLIP-39 shards to a single file.

    Writes multiple shards to a single file, one shard per line.
    Implements Phase 6 file format with enhanced comments and UTF-8 encoding.

    Args:
        shards: List of shard strings to write.
        file_path: Path where to write the file.

    Raises:
        FileError: If file cannot be written.
    """
    try:
        # Convert to Path and sanitize only the filename component
        file_path_obj = Path(file_path)
        safe_filename = sanitize_filename(file_path_obj.name)
        safe_path = file_path_obj.parent / safe_filename

        # Create directory if it doesn't exist
        safe_path.parent.mkdir(parents=True, exist_ok=True)

        logger.info("Writing %d shards to file: %s", len(shards), safe_path)

        # Write with UTF-8 encoding and enhanced comments (Phase 6)
        with open(safe_path, "w", encoding="utf-8") as f:
            # Enhanced header comments (Phase 6 requirement)
            f.write("# SLIP-39 Shards File\n")
            f.write(
                f"# Generated by sseed on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
            )
            f.write(f"# Contains {len(shards)} SLIP-39 shards\n")
            f.write("#\n")
            f.write("# This file contains multiple SLIP-39 shards for mnemonic reconstruction.\n")
            f.write("# Keep this file secure and consider splitting shards into separate files.\n")
            f.write(
                "# You need at least the threshold number of shards to "
                "reconstruct the original mnemonic.\n"
            )
            f.write("#\n")
            f.write("# File format: Plain text UTF-8\n")
            f.write("# Lines starting with '#' are comments and will be ignored.\n")
            f.write("# Each shard is on its own line after the shard number comment.\n")
            f.write("#\n")

            # Write each shard with individual comments
            for i, shard in enumerate(shards, 1):
                f.write(f"# Shard {i} of {len(shards)}\n")
                f.write(shard + "\n")
                f.write("\n")  # Empty line between shards for readability

        logger.info("Successfully wrote %d shards to file: %s", len(shards), safe_path)

    except Exception as e:
        error_msg = f"Failed to write shards to file '{file_path}': {e}"
        logger.error(error_msg)
        raise FileError(
            error_msg,
            context={
                "file_path": str(file_path),
                "shard_count": len(shards),
                "error": str(e),
            },
        ) from e


def write_shards_to_separate_files(shards: list[str], base_path: str) -> list[str]:
    """Write SLIP-39 shards to separate files.

    Writes each shard to a separate file with numbered suffixes.
    Implements Phase 6 file format with UTF-8 encoding and comment support.

    Args:
        shards: List of shard strings to write.
        base_path: Base path for the files (e.g., "shards.txt" -> "shards_01.txt", "shards_02.txt").

    Returns:
        List of file paths where shards were written.

    Raises:
        FileError: If any file cannot be written.
    """
    try:
        base_path_obj = Path(base_path)
        stem = base_path_obj.stem or "shard"
        suffix = base_path_obj.suffix or ".txt"
        directory = base_path_obj.parent

        # Create directory if it doesn't exist
        directory.mkdir(parents=True, exist_ok=True)

        file_paths = []

        for i, shard in enumerate(shards, 1):
            # Create numbered filename (Phase 6 feature)
            filename = f"{stem}_{i:02d}{suffix}"
            file_path = directory / filename

            # Write individual shard with enhanced comment format (Phase 6)
            _write_shard_with_comments(shard, str(file_path), i, len(shards))
            file_paths.append(str(file_path))

        logger.info("Successfully wrote %d shards to separate files", len(shards))
        return file_paths

    except Exception as e:
        error_msg = f"Failed to write shards to separate files: {e}"
        logger.error(error_msg)
        raise FileError(
            error_msg,
            context={
                "base_path": str(base_path),
                "shard_count": len(shards),
                "error": str(e),
            },
        ) from e


def _write_shard_with_comments(
    shard: str, file_path: str, shard_num: int, total_shards: int
) -> None:
    """Write a single shard with enhanced comment format.

    Implements Phase 6 file format requirements:
    - Plain text UTF-8 encoding
    - Comment support with metadata
    - Cross-platform compatibility

    Args:
        shard: SLIP-39 shard string.
        file_path: Path where to write the file.
        shard_num: Current shard number (1-based).
        total_shards: Total number of shards.

    Raises:
        FileError: If file cannot be written.
    """
    try:
        # Convert to Path and sanitize only the filename component
        file_path_obj = Path(file_path)
        safe_filename = sanitize_filename(file_path_obj.name)
        safe_path = file_path_obj.parent / safe_filename

        # Create directory if it doesn't exist
        safe_path.parent.mkdir(parents=True, exist_ok=True)

        logger.debug("Writing shard %d to file: %s", shard_num, safe_path)

        # Write with UTF-8 encoding and enhanced comments (Phase 6)
        with open(safe_path, "w", encoding="utf-8") as f:
            # Enhanced comment header (Phase 6 requirement)
            f.write("# SLIP-39 Shard File\n")
            f.write(
                f"# Generated by sseed on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
            )
            f.write(f"# Shard {shard_num} of {total_shards}\n")
            f.write("#\n")
            f.write("# This file contains a SLIP-39 shard for mnemonic reconstruction.\n")
            f.write("# Keep this shard secure and separate from other shards.\n")
            f.write(
                "# You need at least the threshold number of shards to "
                "reconstruct the original mnemonic.\n"
            )
            f.write("#\n")
            f.write("# File format: Plain text UTF-8\n")
            f.write("# Lines starting with '#' are comments and will be ignored.\n")
            f.write("#\n")

            # Write the actual shard
            f.write(shard + "\n")

        logger.debug("Successfully wrote shard to file: %s", safe_path)

    except Exception as e:
        error_msg = f"Failed to write shard to file '{file_path}': {e}"
        logger.error(error_msg)
        raise FileError(
            error_msg,
            context={
                "file_path": str(file_path),
                "shard_num": shard_num,
                "error": str(e),
            },
        ) from e


def read_from_stdin() -> str:
    """Read input from stdin.

    Reads and normalizes input from stdin, handling comment lines.

    Returns:
        Normalized input string.

    Raises:
        FileError: If reading from stdin fails.
    """
    try:
        logger.info("Reading input from stdin")

        lines = []
        for line in sys.stdin:
            normalized_line = normalize_input(line)

            # Skip empty lines and comments
            if normalized_line and not normalized_line.startswith("#"):
                lines.append(normalized_line)

        if not lines:
            raise FileError(
                "No valid input received from stdin",
                context={"lines_read": len(lines)},
            )

        # Join all non-comment lines (in case input spans multiple lines)
        result = " ".join(lines)
        logger.info("Successfully read input from stdin")

        return result

    except FileError:
        # Re-raise file errors as-is
        raise
    except Exception as e:
        error_msg = f"Failed to read from stdin: {e}"
        logger.error(error_msg)
        raise FileError(error_msg, context={"error": str(e)}) from e


def write_to_stdout(content: str) -> None:
    """Write content to stdout.

    Writes content to stdout with proper encoding.

    Args:
        content: Content to write.

    Raises:
        FileError: If writing to stdout fails.
    """
    try:
        logger.info("Writing output to stdout")

        print(content)
        sys.stdout.flush()

        logger.info("Successfully wrote output to stdout")

    except Exception as e:
        error_msg = f"Failed to write to stdout: {e}"
        logger.error(error_msg)
        raise FileError(error_msg, context={"error": str(e)}) from e
