"""File upload commands for knowledge base."""

import fnmatch
from pathlib import Path
from typing import Annotated, Optional

import typer
from rich.table import Table

from paracord.utils.console import (
    console,
    print_header,
    print_success,
    print_error,
    print_warning,
    print_info,
)
from paracord.core.api import ParacordClient, AuthenticationError

upload_app = typer.Typer(
    name="upload",
    help="Upload files to knowledge base.",
    no_args_is_help=True,
)

# Supported file extensions for upload
SUPPORTED_EXTENSIONS = {
    ".pdf", ".doc", ".docx", ".txt", ".md",
    ".xlsx", ".csv", ".json", ".yaml", ".yml"
}

# Default patterns to ignore (always applied)
DEFAULT_IGNORES = [
    ".git/",
    ".git",
    "__pycache__/",
    "__pycache__",
    "node_modules/",
    "node_modules",
    ".DS_Store",
    "*.pyc",
    ".env",
    ".env.*",
    "*.egg-info/",
    "*.egg-info",
    ".venv/",
    ".venv",
    "venv/",
    "venv",
]


def parse_gitignore(directory: Path) -> list[str]:
    """Parse .gitignore file and return list of patterns."""
    gitignore_path = directory / ".gitignore"
    patterns = []

    if gitignore_path.exists():
        with open(gitignore_path) as f:
            for line in f:
                line = line.strip()
                # Skip empty lines and comments
                if line and not line.startswith("#"):
                    patterns.append(line)

    return patterns


def should_ignore(file_path: Path, base_dir: Path, patterns: list[str]) -> bool:
    """Check if a file should be ignored based on patterns."""
    relative_path = file_path.relative_to(base_dir)
    relative_str = str(relative_path)

    for pattern in patterns:
        # Handle directory patterns (ending with /)
        if pattern.endswith("/"):
            dir_pattern = pattern.rstrip("/")
            # Check if any parent directory matches
            for parent in relative_path.parents:
                if fnmatch.fnmatch(str(parent), dir_pattern):
                    return True
                if str(parent) == dir_pattern:
                    return True

        # Handle negation patterns
        if pattern.startswith("!"):
            # Negation patterns are not fully supported in this simple implementation
            continue

        # Check direct match
        if fnmatch.fnmatch(relative_str, pattern):
            return True

        # Check filename match
        if fnmatch.fnmatch(file_path.name, pattern):
            return True

        # Check if pattern matches any part of the path
        for part in relative_path.parts:
            if fnmatch.fnmatch(part, pattern):
                return True

    return False


def collect_files(
    directory: Path,
    pattern: Optional[str] = None,
    use_gitignore: bool = True,
) -> list[Path]:
    """Collect files from directory, applying filters."""
    files = []

    # Build ignore patterns
    ignore_patterns = DEFAULT_IGNORES.copy()
    if use_gitignore:
        ignore_patterns.extend(parse_gitignore(directory))

    # Walk directory
    for file_path in directory.rglob("*"):
        if not file_path.is_file():
            continue

        # Check extension
        if file_path.suffix.lower() not in SUPPORTED_EXTENSIONS:
            continue

        # Check ignore patterns
        if should_ignore(file_path, directory, ignore_patterns):
            continue

        # Check user-provided pattern filter
        if pattern and not fnmatch.fnmatch(file_path.name, pattern):
            continue

        files.append(file_path)

    return sorted(files)


@upload_app.command(name="files")
def upload_files(
    directory: Annotated[
        Path,
        typer.Argument(help="Directory containing files to upload"),
    ],
    dry_run: Annotated[
        bool,
        typer.Option("--dry-run", "-n", help="Preview files without uploading"),
    ] = False,
    pattern: Annotated[
        Optional[str],
        typer.Option("--pattern", "-p", help="Filter files by glob pattern (e.g., '*.md')"),
    ] = None,
    no_gitignore: Annotated[
        bool,
        typer.Option("--no-gitignore", help="Ignore .gitignore file"),
    ] = False,
) -> None:
    """Upload files from a directory to the knowledge base."""
    print_header("Upload Files")

    # Validate directory
    if not directory.exists():
        print_error(f"Directory not found: {directory}")
        raise typer.Exit(1)

    if not directory.is_dir():
        print_error(f"Not a directory: {directory}")
        raise typer.Exit(1)

    # Resolve to absolute path
    directory = directory.resolve()

    # Collect files
    use_gitignore = not no_gitignore
    files = collect_files(directory, pattern=pattern, use_gitignore=use_gitignore)

    if not files:
        print_warning("No files found to upload")
        print_info(f"Supported extensions: {', '.join(sorted(SUPPORTED_EXTENSIONS))}")
        return

    # Display files
    table = Table(title=f"Files to Upload ({len(files)})")
    table.add_column("File", style="path")
    table.add_column("Size", justify="right")
    table.add_column("Type")

    total_size = 0
    for file_path in files:
        size = file_path.stat().st_size
        total_size += size
        relative_path = file_path.relative_to(directory)

        # Format size
        if size < 1024:
            size_str = f"{size} B"
        elif size < 1024 * 1024:
            size_str = f"{size / 1024:.1f} KB"
        else:
            size_str = f"{size / (1024 * 1024):.1f} MB"

        table.add_row(
            str(relative_path),
            size_str,
            file_path.suffix.lower(),
        )

    console.print(table)

    # Format total size
    if total_size < 1024:
        total_size_str = f"{total_size} B"
    elif total_size < 1024 * 1024:
        total_size_str = f"{total_size / 1024:.1f} KB"
    else:
        total_size_str = f"{total_size / (1024 * 1024):.1f} MB"

    console.print(f"\n[info]Total size:[/info] {total_size_str}")

    if dry_run:
        print_info("Dry run - no files uploaded")
        return

    # Authenticate
    try:
        client = ParacordClient()
    except AuthenticationError as e:
        print_error(str(e))
        raise typer.Exit(1)

    # Upload files
    created = 0
    updated = 0
    unchanged = 0
    failed = 0

    console.print("\n[info]Uploading files...[/info]")

    for file_path in files:
        relative_path = file_path.relative_to(directory)
        local_path = str(relative_path)

        try:
            response = client.upload_file(
                "/cli/files/upload/",
                file_path,
                local_path,
            )

            if response.status_code in (200, 201):
                data = response.json()
                action = data.get("action", "created")

                if action == "skipped":
                    console.print(f"  [dim]Unchanged:[/dim] {local_path}")
                    unchanged += 1
                elif action == "updated":
                    console.print(f"  [cyan]Updated:[/cyan] {local_path}")
                    updated += 1
                else:
                    console.print(f"  [green]Created:[/green] {local_path}")
                    created += 1
            else:
                error_msg = response.text[:100] if response.text else "Unknown error"
                console.print(f"  [red]Failed:[/red] {local_path} - {response.status_code}: {error_msg}")
                failed += 1
        except Exception as e:
            console.print(f"  [red]Failed:[/red] {local_path} - {e}")
            failed += 1

    # Summary
    console.print()
    summary_parts = []
    if created > 0:
        summary_parts.append(f"{created} created")
    if updated > 0:
        summary_parts.append(f"{updated} updated")
    if unchanged > 0:
        summary_parts.append(f"{unchanged} unchanged")
    if failed > 0:
        summary_parts.append(f"{failed} failed")

    if failed == 0:
        print_success(f"Upload complete: {', '.join(summary_parts)}")
    else:
        print_warning(f"Upload complete: {', '.join(summary_parts)}")


@upload_app.command(name="status")
def upload_status(
    uid: Annotated[
        Optional[str],
        typer.Argument(help="File UID to check status for (optional)"),
    ] = None,
) -> None:
    """List uploaded files or check processing status of a specific file."""
    print_header("Upload Status")

    try:
        client = ParacordClient()
    except AuthenticationError as e:
        print_error(str(e))
        raise typer.Exit(1)

    if uid:
        # Get specific file status
        response = client.get(f"/cli/files/{uid}/status/")

        if response.status_code == 404:
            print_error(f"File not found: {uid}")
            raise typer.Exit(1)

        if response.status_code != 200:
            print_error(f"Failed to get file status: {response.status_code}")
            raise typer.Exit(1)

        file_data = response.json()

        console.print(f"[bold]File:[/bold] {file_data.get('filename', 'N/A')}")
        console.print(f"[bold]UID:[/bold] {file_data.get('uid', uid)}")
        console.print(f"[bold]Local Path:[/bold] {file_data.get('local_path', 'N/A')}")
        console.print(f"[bold]MIME Type:[/bold] {file_data.get('mime_type', 'N/A')}")
        console.print(f"[bold]File Size:[/bold] {file_data.get('file_size', 0):,} bytes")
        console.print(f"[bold]Uploaded:[/bold] {file_data.get('uploaded_at', 'N/A')}")
        console.print()
        console.print("[bold]Processing Status:[/bold]")
        console.print(f"  Has Content: {'Yes' if file_data.get('has_content') else 'No'}")
        console.print(f"  Chunks Created: {file_data.get('chunk_count', 0)}")
        console.print(f"  Entities Extracted: {'Yes' if file_data.get('entities_extracted') else 'No'}")
        console.print(f"  Processes Linked: {file_data.get('processes_linked', 0)}")
        console.print(f"  Tools Linked: {file_data.get('tools_linked', 0)}")
    else:
        # List all files
        response = client.get("/cli/files/")

        if response.status_code != 200:
            print_error(f"Failed to list files: {response.status_code}")
            if response.status_code == 401:
                print_info("Your token may have expired. Run 'paracord auth login' to re-authenticate.")
            raise typer.Exit(1)

        data = response.json()

        # Handle paginated response from the API
        files = data.get("files", [])
        total = data.get("total", len(files))

        if not files:
            print_info("No files uploaded yet")
            print_info("Use 'paracord upload files <directory>' to upload files")
            return

        table = Table(title=f"Uploaded Files ({total})")
        table.add_column("UID", style="dim")
        table.add_column("Filename")
        table.add_column("Local Path", style="path")
        table.add_column("Size", justify="right")
        table.add_column("Uploaded")

        for file_data in files:
            uid_val = file_data.get("uid", "N/A")
            # Truncate UID for display
            uid_display = uid_val[:8] + "..." if len(uid_val) > 11 else uid_val

            # Format size
            size = file_data.get("file_size", 0)
            if size < 1024:
                size_str = f"{size} B"
            elif size < 1024 * 1024:
                size_str = f"{size / 1024:.1f} KB"
            else:
                size_str = f"{size / (1024 * 1024):.1f} MB"

            # Format date
            uploaded_at = file_data.get("uploaded_at", "")
            uploaded_display = uploaded_at[:10] if uploaded_at else "N/A"

            table.add_row(
                uid_display,
                file_data.get("filename", "N/A"),
                file_data.get("local_path", "N/A"),
                size_str,
                uploaded_display,
            )

        console.print(table)
        print_info("Use 'paracord upload status <uid>' to see details for a specific file")


@upload_app.command(name="delete")
def upload_delete(
    uid: Annotated[
        str,
        typer.Argument(help="File UID to delete"),
    ],
    force: Annotated[
        bool,
        typer.Option("--force", "-f", help="Skip confirmation"),
    ] = False,
) -> None:
    """Delete an uploaded file."""
    print_header("Delete File")

    try:
        client = ParacordClient()
    except AuthenticationError as e:
        print_error(str(e))
        raise typer.Exit(1)

    # First get file info
    response = client.get(f"/cli/files/{uid}/status/")

    if response.status_code == 404:
        print_error(f"File not found: {uid}")
        raise typer.Exit(1)

    if response.status_code != 200:
        print_error(f"Failed to get file info: {response.status_code}")
        raise typer.Exit(1)

    file_data = response.json()
    filename = file_data.get("filename", uid)

    # Confirm deletion
    if not force:
        console.print(f"[warning]About to delete:[/warning] {filename}")
        confirm = typer.confirm("Are you sure?")
        if not confirm:
            print_info("Cancelled")
            return

    # Delete file
    response = client.delete(f"/cli/files/{uid}/")

    if response.status_code in (200, 204):
        print_success(f"Deleted: {filename}")
    else:
        print_error(f"Failed to delete file: {response.status_code}")
        raise typer.Exit(1)
