"""
Submit command - Submit automation for review.

This module provides functionality to package and submit an automation
to the ToRivers marketplace for review.
"""

from __future__ import annotations

import fnmatch
import json
import tempfile
import zipfile
from datetime import datetime, timezone
from pathlib import Path

import httpx
import yaml
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn

from ._config import (
    extract_error_message,
    get_api_base_url,
    get_auth_token,
    is_authenticated,
)
from .validate import validate_automation

console = Console()

# Files/directories to exclude from submission
EXCLUDE_PATTERNS = [
    "__pycache__",
    "*.pyc",
    "*.pyo",
    ".git",
    ".gitignore",
    ".env",
    ".env.*",
    "*.egg-info",
    ".pytest_cache",
    ".mypy_cache",
    ".coverage",
    "htmlcov",
    ".venv",
    "venv",
    "node_modules",
    ".DS_Store",
    "Thumbs.db",
    "*.log",
]


def submit_automation(
    message: str | None = None,
    dry_run: bool = False,
) -> bool:
    """
    Submit the automation for review.

    Args:
        message: Optional submission message
        dry_run: If True, validate without submitting

    Returns:
        True if successful, False otherwise
    """
    # Check we're in an automation project
    if not _is_automation_project():
        console.print("[red]Error:[/red] Not in an automation project directory")
        return False

    # Check authentication
    if not dry_run and not is_authenticated():
        console.print("[red]Error:[/red] Not authenticated")
        console.print("[dim]Run 'torivers login' to authenticate[/dim]")
        return False

    console.print(
        Panel.fit(
            (
                "[bold]Submitting Automation for Review[/bold]"
                if not dry_run
                else "[bold]Dry Run - Validating Submission[/bold]"
            ),
            subtitle="This may take a moment",
        )
    )
    console.print()

    # Run validation first
    console.print("[dim]Step 1: Validating automation...[/dim]")
    if not validate_automation(strict=True):
        console.print("\n[red]Submission aborted due to validation errors[/red]")
        return False

    console.print()

    if dry_run:
        console.print(
            Panel.fit(
                "[green]✓ Dry run passed![/green]\n\n"
                "Your automation is ready for submission.\n"
                "Run without --dry-run to submit.",
                title="Dry Run Complete",
            )
        )
        return True

    # Package the automation
    console.print("[dim]Step 2: Packaging automation...[/dim]")
    package_path = _create_package()
    if package_path is None:
        return False

    console.print(f"[dim]Package created: {package_path.name}[/dim]")
    console.print(f"[dim]Size: {package_path.stat().st_size / 1024:.2f} KB[/dim]")

    # Submit to ToRivers
    console.print("\n[dim]Step 3: Uploading to ToRivers...[/dim]")
    submission_id = _upload_package(package_path, message)

    if submission_id is None:
        console.print("[red]Error:[/red] Failed to upload package")
        return False

    # Clean up
    package_path.unlink()

    console.print()
    console.print(
        Panel.fit(
            f"[green]✓ Automation submitted successfully![/green]\n\n"
            f"Submission ID: [cyan]{submission_id}[/cyan]\n\n"
            "Your automation is now in review.\n"
            "Run 'torivers status' to check the review status.",
            title="Submission Complete",
        )
    )

    return True


def _is_automation_project() -> bool:
    """Check if the current directory is an automation project."""
    cwd = Path.cwd()
    return (cwd / "automation.yaml").exists() or (cwd / "main.py").exists()


def _create_package() -> Path | None:
    """Create a zip package of the automation."""
    try:
        cwd = Path.cwd()
        exclude_patterns = _get_exclude_patterns(cwd)

        # Generate package name
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
        package_name = f"automation_{timestamp}.zip"
        package_path = Path(tempfile.gettempdir()) / package_name

        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=console,
        ) as progress:
            task = progress.add_task("Packaging files...", total=None)

            with zipfile.ZipFile(package_path, "w", zipfile.ZIP_DEFLATED) as zf:
                for file_path in cwd.rglob("*"):
                    if file_path.is_file() and not _should_exclude(
                        file_path, cwd, exclude_patterns
                    ):
                        arcname = file_path.relative_to(cwd)
                        zf.write(file_path, arcname)
                        progress.update(task, description=f"Adding: {arcname}")

            progress.update(task, description="Package complete!")

        return package_path

    except Exception as e:
        console.print(f"[red]Error creating package:[/red] {e}")
        return None


def _load_gitignore_patterns(base_path: Path) -> list[str]:
    """Load basic ignore patterns from .gitignore in the project root."""
    gitignore_path = base_path / ".gitignore"
    if not gitignore_path.exists():
        return []

    patterns: list[str] = []
    for raw_line in gitignore_path.read_text().splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue
        # Negation patterns are ignored for packaging safety: excluded by default.
        if line.startswith("!"):
            continue
        line = line.lstrip("/").rstrip("/")
        if line:
            patterns.append(line)
    return patterns


def _get_exclude_patterns(base_path: Path) -> list[str]:
    """Get effective exclude patterns for packaging."""
    return list(
        dict.fromkeys([*EXCLUDE_PATTERNS, *_load_gitignore_patterns(base_path)])
    )


def _matches_pattern(relative: Path, pattern: str) -> bool:
    """Check if a file path matches an exclusion pattern."""
    relative_posix = relative.as_posix()

    # Match full relative path (e.g. foo/*.py, .env.*)
    if fnmatch.fnmatch(relative_posix, pattern):
        return True

    # Match any path segment (e.g. __pycache__, .venv, *.pyc)
    for part in relative.parts:
        if fnmatch.fnmatch(part, pattern):
            return True

    return False


def _should_exclude(
    file_path: Path, base_path: Path, exclude_patterns: list[str] | None = None
) -> bool:
    """Check if a file should be excluded from the package."""
    relative = file_path.relative_to(base_path)
    patterns = exclude_patterns or EXCLUDE_PATTERNS

    for pattern in patterns:
        if _matches_pattern(relative, pattern):
            return True

    return False


def _upload_package(package_path: Path, message: str | None) -> str | None:
    """Upload the package to ToRivers."""
    token = get_auth_token()
    manifest = _load_manifest()

    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        console=console,
    ) as progress:
        task = progress.add_task("Uploading...", total=None)

        progress.update(task, description="Preparing request...")

        endpoint = f"{get_api_base_url()}/developer/submissions"
        payload = {
            "manifest": json.dumps(manifest),
            "source": "sdk",
        }
        if message:
            payload["message"] = message

        try:
            with open(package_path, "rb") as bundle_file:
                files = {
                    "bundle": (
                        package_path.name,
                        bundle_file,
                        "application/zip",
                    )
                }
                with httpx.Client(timeout=90.0) as client:
                    response = client.post(
                        endpoint,
                        data=payload,
                        files=files,
                        headers={
                            "Authorization": f"Bearer {token}",
                        },
                    )
        except httpx.TimeoutException:
            console.print("[red]Error:[/red] Submission request timed out")
            return None
        except httpx.HTTPError as exc:
            console.print(f"[red]Error:[/red] Failed to connect to API: {exc}")
            return None

        if response.status_code not in (200, 201):
            error_message = extract_error_message(response)
            console.print(f"[red]Error:[/red] Submission failed: {error_message}")
            return None

        response_data = response.json() if response.content else {}
        submission_id = response_data.get("submission_id") or response_data.get("id")
        if not submission_id:
            console.print("[red]Error:[/red] Submission API returned no submission ID")
            return None

        progress.update(task, description="Upload complete!")

    return submission_id


def _load_manifest() -> dict:
    manifest_path = Path.cwd() / "automation.yaml"
    if not manifest_path.exists():
        raise RuntimeError("automation.yaml not found in current directory")

    try:
        with open(manifest_path) as f:
            manifest = yaml.safe_load(f) or {}
    except (yaml.YAMLError, OSError) as exc:
        raise RuntimeError(f"Failed to parse automation.yaml: {exc}") from exc

    if not isinstance(manifest, dict):
        raise RuntimeError("automation.yaml must contain a YAML object")
    if not manifest.get("name") or not manifest.get("version"):
        raise RuntimeError("automation.yaml must include 'name' and 'version'")

    return manifest
