"""Inventory command - manage component inventory."""

import argparse
import csv
import sys
from pathlib import Path
from datetime import datetime
import shutil

from jbom.common.types import Component, InventoryItem
from jbom.common.options import GeneratorOptions
from jbom.cli.formatting import print_inventory_table
from jbom.services.inventory_reader import InventoryReader
from jbom.services.project_file_resolver import ProjectFileResolver
from jbom.services.project_inventory import ProjectInventoryGenerator
from jbom.services.schematic_reader import SchematicReader
from jbom.services.sophisticated_inventory_matcher import (
    MatchingOptions,
    SophisticatedInventoryMatcher,
)


def register_command(subparsers) -> None:
    """Register inventory command with argument parser."""
    parser = subparsers.add_parser(
        "inventory",
        help="Generate component inventory from project",
        description="Generate component inventory from project",
    )

    # Project input as main positional argument
    parser.add_argument(
        "input",
        nargs="?",
        default=".",
        help="Path to .kicad_sch file, project directory, or base name (default: current directory)",
    )

    # Output options
    parser.add_argument(
        "-o",
        "--output",
        help="Output destination: file path, 'console', or '-' for stdout (default: part-inventory.csv)",
        default=None,
    )

    # Inventory merge options
    parser.add_argument(
        "--inventory",
        help="Path to existing inventory CSV file for merge operations (can be specified multiple times)",
        type=Path,
        action="append",
        dest="inventory_files",
    )

    parser.add_argument(
        "--filter-matches",
        action="store_true",
        help="Filter out components that match existing inventory items",
    )

    # Safety options
    parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite existing output files without confirmation",
    )

    # Verbose mode
    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")

    parser.set_defaults(handler=handle_inventory)


def handle_inventory(args: argparse.Namespace) -> int:
    """Handle inventory command - generate inventory from project."""
    try:
        return _handle_generate_inventory(args)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1


def _handle_generate_inventory(args: argparse.Namespace) -> int:
    """Generate inventory from project components with project-centric input resolution.

    Output destination handling (following BOM command pattern):
    - None (default): write to 'part-inventory.csv'
    - "console": pretty print table to stdout
    - "-": write CSV format to stdout
    - otherwise: treat as a file path
    """

    # Create options
    options = GeneratorOptions(verbose=args.verbose) if args.verbose else None

    # Use ProjectFileResolver for intelligent input resolution
    resolver = ProjectFileResolver(
        prefer_pcb=False, target_file_type="schematic", options=options
    )

    try:
        resolved_input = resolver.resolve_input(args.input)

        # Handle cross-command intelligence - if user provided wrong file type, try to resolve it
        if not resolved_input.is_schematic:
            if args.verbose:
                print(
                    f"Note: Inventory generation requires a schematic file. "
                    f"Found {resolved_input.resolved_path.suffix} file, trying to find matching schematic.",
                    file=sys.stderr,
                )

            resolved_input = resolver.resolve_for_wrong_file_type(
                resolved_input, "schematic"
            )
            if args.verbose:
                print(
                    f"Using schematic: {resolved_input.resolved_path.name}",
                    file=sys.stderr,
                )

        schematic_file = resolved_input.resolved_path
        reader = SchematicReader(options)

        # Load components from schematic (including hierarchical sheets if available)
        if resolved_input.project_context:
            # Get all hierarchical schematic files for complete inventory
            hierarchical_files = resolved_input.get_hierarchical_files()
            if args.verbose and len(hierarchical_files) > 1:
                print(
                    f"Processing hierarchical design with {len(hierarchical_files)} schematic files",
                    file=sys.stderr,
                )

            # Load components from all hierarchical files
            components = []
            for sch_file in hierarchical_files:
                if args.verbose:
                    print(f"Loading components from {sch_file.name}", file=sys.stderr)
                file_components = reader.load_components(sch_file)
                components.extend(file_components)
        else:
            # Load components from single schematic
            components = reader.load_components(schematic_file)

    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1

    # Check for empty components list
    if not components:
        print(
            "Error: No components found in project. Cannot create inventory from empty schematic.",
            file=sys.stderr,
        )
        return 1

    # Apply inventory filtering (component-level) if requested.
    if args.inventory_files or args.filter_matches:
        components = _filter_components_by_existing_inventory(
            components,
            inventory_files=args.inventory_files,
            filter_matches=args.filter_matches,
            verbose=args.verbose,
        )

    # Generate inventory
    generator = ProjectInventoryGenerator(components)
    inventory_items, field_names = generator.load()

    # Handle output using same pattern as BOM command
    return _output_inventory(
        inventory_items, field_names, args.output, args.force, args.verbose
    )


def _filter_components_by_existing_inventory(
    components: list[Component],
    *,
    inventory_files: list[Path] | None,
    filter_matches: bool,
    verbose: bool,
) -> list[Component]:
    """Filter project components based on whether they match an existing inventory.

    This is used by `jbom inventory` to show only "new" components not already
    represented in an inventory file.

    Args:
        components: Components loaded from schematic(s).
        inventory_files: Inventory CSV file paths.
        filter_matches: If True, exclude components that match (show only new).
        verbose: Emit diagnostic match info to stderr.

    Returns:
        Filtered component list.
    """
    inventory_files = inventory_files or []
    if not inventory_files:
        if filter_matches:
            print(
                "Error: --filter-matches requires --inventory file(s)",
                file=sys.stderr,
            )
            raise SystemExit(1)
        return components

    # Load and merge inventory files (first occurrence of each IPN wins).
    merged_inventory: list[InventoryItem] = []
    seen_ipns: set[str] = set()

    if verbose:
        print(
            f"Loading {len(inventory_files)} inventory file(s):",
            file=sys.stderr,
        )

    missing_file_detected = False
    total_files_loaded = 0

    for i, inventory_file in enumerate(inventory_files):
        if not inventory_file.exists():
            missing_file_detected = True
            print(
                f"Error: Inventory file not found: {inventory_file}",
                file=sys.stderr,
            )
            continue

        try:
            reader = InventoryReader(inventory_file)
            file_inventory, _ = reader.load()

            added_count = 0
            for item in file_inventory:
                if item.ipn not in seen_ipns:
                    merged_inventory.append(item)
                    seen_ipns.add(item.ipn)
                    added_count += 1

            total_files_loaded += 1
            if verbose:
                file_desc = f"file {i + 1}"
                print(
                    f"  {file_desc}: {inventory_file} ({added_count}/{len(file_inventory)} items added)",
                    file=sys.stderr,
                )

        except Exception as e:
            print(f"Error loading {inventory_file}: {e}", file=sys.stderr)

    if missing_file_detected:
        raise SystemExit(1)

    if not merged_inventory:
        print("Error: No inventory items loaded from any file", file=sys.stderr)
        raise SystemExit(1)

    if verbose:
        print(
            f"Merged inventory: {len(merged_inventory)} total items from {total_files_loaded} file(s)",
            file=sys.stderr,
        )

    matcher = SophisticatedInventoryMatcher(MatchingOptions(include_debug_info=verbose))

    filtered_components: list[Component] = []
    matched_count = 0

    for comp in components:
        matches = matcher.find_matches(comp, merged_inventory)

        if matches:
            matched_count += 1
            if verbose:
                print(
                    f"Matched {comp.reference}: {matches[0].debug_info}",
                    file=sys.stderr,
                )

            if not filter_matches:
                filtered_components.append(comp)
        else:
            if verbose:
                print(
                    f"No match for {comp.reference} ({comp.lib_id} {comp.value} {comp.footprint})",
                    file=sys.stderr,
                )
            filtered_components.append(comp)

    if verbose:
        total = len(components)
        filtered = len(filtered_components)
        action = "kept" if filter_matches else "included"
        print(
            f"\nInventory filtering: {matched_count}/{total} matched, {filtered} {action}",
            file=sys.stderr,
        )

    return filtered_components


def _output_inventory(
    inventory_items, field_names, output, force=False, verbose=False
) -> int:
    """Output inventory data in the requested format.

    Human-first defaults (BREAKING CHANGE for UX consistency):
    - output is None => formatted table to stdout (human exploration)
    - output == "console" => formatted table to stdout (explicit)
    - output == "-" => CSV to stdout (machine readable)
    - otherwise => treat as file path with safety checks
    """
    if output is None or output == "console":
        _print_console_table(inventory_items, field_names)
    elif output == "-":
        _print_csv(inventory_items, field_names)
    else:
        output_path = Path(output)

        # File existence and backup handling
        if output_path.exists():
            if not force:
                print(
                    f"Error: Output file '{output_path}' already exists. Use --force to overwrite.",
                    file=sys.stderr,
                )
                return 1

            # Create timestamped backup
            backup_path = _create_backup(output_path, verbose)
            if backup_path and verbose:
                print(f"Created backup: {backup_path}", file=sys.stderr)

        _write_csv(inventory_items, field_names, output_path)
        print(
            f"Generated inventory with {len(inventory_items)} items written to {output_path}"
        )

    return 0


def _create_backup(file_path: Path, verbose: bool = False) -> Path:
    """Create a timestamped backup of an existing file.

    Args:
        file_path: Path to file to backup
        verbose: Enable verbose output

    Returns:
        Path to created backup file, or None if backup failed
    """
    if not file_path.exists():
        return None

    # Generate timestamped backup filename
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_path = file_path.with_name(
        f"{file_path.stem}.backup.{timestamp}{file_path.suffix}"
    )

    try:
        shutil.copy2(file_path, backup_path)
        return backup_path
    except Exception as e:
        if verbose:
            print(f"Warning: Failed to create backup: {e}", file=sys.stderr)
        return None


def _print_console_table(inventory_items, field_names) -> None:
    """Print inventory as formatted console table."""
    # Convert inventory items to dict format for print_inventory_table
    item_dicts = []
    for item in inventory_items:
        row = {
            "IPN": item.ipn,
            "Category": item.category,
            "Value": item.value,
            "Description": item.description,
            "Package": item.package,
            "Manufacturer": item.manufacturer,
            "MFGPN": item.mfgpn,
            "LCSC": item.lcsc,
            "Datasheet": item.datasheet,
            "UUID": item.uuid,
        }
        # Add any extra fields from component properties
        for field in field_names:
            if (
                field not in row
                and hasattr(item, "raw_data")
                and field in item.raw_data
            ):
                row[field] = item.raw_data[field]
        item_dicts.append(row)

    # Use the common formatting utility
    print_inventory_table(item_dicts, field_names)
    print(f"\nGenerated inventory with {len(inventory_items)} items")


def _print_csv(inventory_items, field_names) -> None:
    """Print inventory as CSV to stdout."""
    writer = csv.DictWriter(sys.stdout, fieldnames=field_names)
    writer.writeheader()

    for item in inventory_items:
        row = {
            "IPN": item.ipn,
            "Category": item.category,
            "Value": item.value,
            "Description": item.description,
            "Package": item.package,
            "Manufacturer": item.manufacturer,
            "MFGPN": item.mfgpn,
            "LCSC": item.lcsc,
            "Datasheet": item.datasheet,
            "UUID": item.uuid,
        }
        # Add any extra fields from component properties
        for field in field_names:
            if (
                field not in row
                and hasattr(item, "raw_data")
                and field in item.raw_data
            ):
                row[field] = item.raw_data[field]
        writer.writerow(row)


def _write_csv(inventory_items, field_names, output_path: Path) -> None:
    """Write inventory as CSV to file."""
    with open(output_path, "w", newline="", encoding="utf-8") as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=field_names)
        writer.writeheader()

        for item in inventory_items:
            row = {
                "IPN": item.ipn,
                "Category": item.category,
                "Value": item.value,
                "Description": item.description,
                "Package": item.package,
                "Manufacturer": item.manufacturer,
                "MFGPN": item.mfgpn,
                "LCSC": item.lcsc,
                "Datasheet": item.datasheet,
                "UUID": item.uuid,
            }
            # Add any extra fields from component properties
            for field in field_names:
                if (
                    field not in row
                    and hasattr(item, "raw_data")
                    and field in item.raw_data
                ):
                    row[field] = item.raw_data[field]
            writer.writerow(row)
