"""
CLI commands for Information Spaces.

Provides command-line interface for managing spaces, documents,
rendering, and synchronization.
"""

import click
import json
import os
from pathlib import Path
from typing import Optional
from tabulate import tabulate

from .models import InformationSpace, SpaceConfig, SpaceMetadata, SpaceStatus
from .services.space_service import SpaceService
from .repositories.sqlite import (
    SqliteSpaceRepository,
    SqliteDocumentRepository,
    SqliteVariableRepository,
    SqliteReferenceRepository,
)


def get_db_path() -> Path:
    """Get the database path."""
    db_path = os.environ.get(
        "MARKITECT_SPACES_DB",
        os.path.expanduser("~/.markitect/spaces.db"),
    )
    path = Path(db_path)
    path.parent.mkdir(parents=True, exist_ok=True)
    return path


def get_space_service() -> SpaceService:
    """Create a space service instance."""
    db_path = get_db_path()
    return SpaceService(
        space_repo=SqliteSpaceRepository(db_path),
        document_repo=SqliteDocumentRepository(db_path),
        variable_repo=SqliteVariableRepository(db_path),
        reference_repo=SqliteReferenceRepository(db_path),
    )


@click.group()
def space():
    """Manage Information Spaces."""
    pass


# Space lifecycle commands
@space.command("create")
@click.argument("name")
@click.option("--description", "-d", help="Space description")
@click.option("--theme", help="Default theme for rendering")
@click.option("--variant", default="hierarchical", help="Default export variant")
@click.option("--parent", help="Parent space ID for inheritance")
@click.option("--tags", help="Comma-separated tags")
@click.option("--author", help="Author identifier")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
def create_space(
    name: str,
    description: Optional[str],
    theme: Optional[str],
    variant: str,
    parent: Optional[str],
    tags: Optional[str],
    author: Optional[str],
    json_output: bool,
):
    """Create a new Information Space."""
    try:
        service = get_space_service()

        config = SpaceConfig(
            default_variant=variant,
            theme=theme,
        )

        metadata = SpaceMetadata(
            tags=tags.split(",") if tags else [],
            author=author,
        )

        space = service.create_space(
            name=name,
            description=description,
            config=config,
            metadata=metadata,
            parent_space_id=parent,
        )

        if json_output:
            click.echo(
                json.dumps(
                    {
                        "id": space.id,
                        "name": space.name,
                        "status": space.status.value,
                        "created_at": space.created_at.isoformat() if space.created_at else None,
                    },
                    indent=2,
                )
            )
        else:
            click.echo(f"Created space: {space.name} (ID: {space.id})")
            click.echo(f"Status: {space.status.value}")

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("list")
@click.option("--status", "-s", help="Filter by status")
@click.option("--limit", "-l", default=50, help="Maximum results")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
def list_spaces(status: Optional[str], limit: int, json_output: bool):
    """List all Information Spaces."""
    try:
        service = get_space_service()
        spaces = service.list_spaces(status=status, limit=limit)

        if json_output:
            data = [
                {
                    "id": s.id,
                    "name": s.name,
                    "status": s.status.value,
                    "description": s.description,
                }
                for s in spaces
            ]
            click.echo(json.dumps(data, indent=2))
        else:
            if not spaces:
                click.echo("No spaces found.")
                return

            table_data = [
                [s.name, s.id[:8] + "...", s.status.value, s.description or ""]
                for s in spaces
            ]
            click.echo(
                tabulate(
                    table_data,
                    headers=["Name", "ID", "Status", "Description"],
                    tablefmt="simple",
                )
            )

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("show")
@click.argument("space_id_or_name")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
def show_space(space_id_or_name: str, json_output: bool):
    """Show details of a space."""
    try:
        service = get_space_service()

        # Try by ID first, then by name
        space = service.get_space(space_id_or_name)
        if not space:
            space = service.get_space_by_name(space_id_or_name)

        if not space:
            click.echo(f"Space not found: {space_id_or_name}", err=True)
            raise SystemExit(1)

        documents = service.list_documents(space.id)
        variables = service.list_variables(space.id)

        if json_output:
            data = {
                "id": space.id,
                "name": space.name,
                "description": space.description,
                "status": space.status.value,
                "config": space.config.to_dict() if space.config else {},
                "metadata": space.metadata.to_dict() if hasattr(space.metadata, 'to_dict') else {},
                "document_count": len(documents),
                "variable_count": len(variables),
                "created_at": space.created_at.isoformat() if space.created_at else None,
                "updated_at": space.updated_at.isoformat() if space.updated_at else None,
            }
            click.echo(json.dumps(data, indent=2))
        else:
            click.echo(f"Space: {space.name}")
            click.echo(f"  ID: {space.id}")
            click.echo(f"  Status: {space.status.value}")
            click.echo(f"  Description: {space.description or 'N/A'}")
            click.echo(f"  Documents: {len(documents)}")
            click.echo(f"  Variables: {len(variables)}")
            if space.config:
                click.echo(f"  Theme: {space.config.theme or 'default'}")
                click.echo(f"  Variant: {space.config.default_variant}")

    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("delete")
@click.argument("space_id")
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
def delete_space(space_id: str, force: bool):
    """Delete a space."""
    try:
        service = get_space_service()
        space = service.get_space(space_id)

        if not space:
            click.echo(f"Space not found: {space_id}", err=True)
            raise SystemExit(1)

        if not force:
            click.confirm(f"Delete space '{space.name}'?", abort=True)

        service.delete_space(space_id)
        click.echo(f"Deleted space: {space.name}")

    except click.Abort:
        click.echo("Cancelled.")
    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("activate")
@click.argument("space_id")
def activate_space(space_id: str):
    """Activate a draft space."""
    try:
        service = get_space_service()
        space = service.activate_space(space_id)
        click.echo(f"Activated space: {space.name}")

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("archive")
@click.argument("space_id")
def archive_space(space_id: str):
    """Archive a space."""
    try:
        service = get_space_service()
        space = service.archive_space(space_id)
        click.echo(f"Archived space: {space.name}")

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


# Document commands
@space.command("add-doc")
@click.argument("space_id")
@click.option("--path", "-p", required=True, help="Path within space")
@click.option("--document-id", "-d", help="Document ID (generated if not provided)")
@click.option("--content", "-c", help="Markdown content")
@click.option("--file", "-f", "file_path", type=click.Path(exists=True), help="File to read content from")
def add_document(
    space_id: str,
    path: str,
    document_id: Optional[str],
    content: Optional[str],
    file_path: Optional[str],
):
    """Add a document to a space."""
    try:
        service = get_space_service()

        # Read content from file if provided
        if file_path:
            content = Path(file_path).read_text(encoding="utf-8")

        if not document_id:
            import uuid
            document_id = str(uuid.uuid4())

        doc = service.add_document(
            space_id=space_id,
            document_id=document_id,
            space_path=path,
        )

        click.echo(f"Added document: {path}")
        click.echo(f"  Document ID: {doc.document_id}")

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("remove-doc")
@click.argument("space_id")
@click.argument("space_path")
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
def remove_document(space_id: str, space_path: str, force: bool):
    """Remove a document from a space."""
    try:
        service = get_space_service()

        if not force:
            click.confirm(f"Remove document '{space_path}'?", abort=True)

        service.remove_document(space_id, space_path)
        click.echo(f"Removed document: {space_path}")

    except click.Abort:
        click.echo("Cancelled.")
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("list-docs")
@click.argument("space_id")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
def list_documents(space_id: str, json_output: bool):
    """List documents in a space."""
    try:
        service = get_space_service()
        documents = service.list_documents(space_id)

        if json_output:
            data = [
                {
                    "document_id": d.document_id,
                    "space_path": d.space_path,
                    "order_index": d.order_index,
                }
                for d in documents
            ]
            click.echo(json.dumps(data, indent=2))
        else:
            if not documents:
                click.echo("No documents in space.")
                return

            table_data = [
                [d.space_path, d.document_id[:8] + "...", d.order_index]
                for d in documents
            ]
            click.echo(
                tabulate(
                    table_data,
                    headers=["Path", "Doc ID", "Order"],
                    tablefmt="simple",
                )
            )

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


# Variable commands
@space.command("set-var")
@click.argument("space_id")
@click.argument("name")
@click.argument("value")
@click.option("--scope", default="space", help="Variable scope (space, document, request)")
def set_variable(space_id: str, name: str, value: str, scope: str):
    """Set a variable in a space."""
    try:
        service = get_space_service()

        # Try to parse value as JSON
        try:
            parsed_value = json.loads(value)
        except json.JSONDecodeError:
            parsed_value = value

        var = service.set_variable(space_id, name, parsed_value, scope)
        click.echo(f"Set variable: {name} = {value}")

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("get-var")
@click.argument("space_id")
@click.argument("name")
def get_variable(space_id: str, name: str):
    """Get a variable from a space."""
    try:
        service = get_space_service()
        var = service.get_variable(space_id, name)

        if var:
            click.echo(f"{name} = {json.dumps(var.value)}")
        else:
            click.echo(f"Variable not found: {name}", err=True)
            raise SystemExit(1)

    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("list-vars")
@click.argument("space_id")
@click.option("--scope", help="Filter by scope")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
def list_variables(space_id: str, scope: Optional[str], json_output: bool):
    """List variables in a space."""
    try:
        service = get_space_service()
        variables = service.list_variables(space_id, scope=scope)

        if json_output:
            data = [
                {"name": v.name, "value": v.value, "scope": v.scope}
                for v in variables
            ]
            click.echo(json.dumps(data, indent=2))
        else:
            if not variables:
                click.echo("No variables defined.")
                return

            table_data = [
                [v.name, json.dumps(v.value)[:40], v.scope]
                for v in variables
            ]
            click.echo(
                tabulate(
                    table_data,
                    headers=["Name", "Value", "Scope"],
                    tablefmt="simple",
                )
            )

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


# Rendering commands
@space.command("render")
@click.argument("space_id")
@click.option("--output", "-o", type=click.Path(), help="Output directory")
@click.option("--theme", default="default", help="Theme name")
@click.option("--toc", is_flag=True, help="Include table of contents")
@click.option("--document", "-d", help="Render specific document path")
def render_space(
    space_id: str,
    output: Optional[str],
    theme: str,
    toc: bool,
    document: Optional[str],
):
    """Render space documents to HTML."""
    try:
        from .rendering import (
            SpaceRenderingService,
            RenderConfig,
            ThemeConfig,
        )

        service = get_space_service()
        space = service.get_space(space_id)

        if not space:
            click.echo(f"Space not found: {space_id}", err=True)
            raise SystemExit(1)

        config = RenderConfig(
            theme=ThemeConfig(name=theme, layers=[theme]),
            include_toc=toc,
        )

        renderer = SpaceRenderingService()

        if document:
            # Render single document
            doc = service.get_document(space_id, document)
            if not doc:
                click.echo(f"Document not found: {document}", err=True)
                raise SystemExit(1)

            result = renderer.render_document(
                content="# Placeholder",  # Would get actual content
                document_id=doc.document_id,
                space_id=space_id,
            )

            if output:
                output_path = Path(output)
                output_path.parent.mkdir(parents=True, exist_ok=True)
                output_path.write_text(result.content, encoding="utf-8")
                click.echo(f"Rendered to: {output}")
            else:
                click.echo(result.content)

        else:
            # Render all documents
            documents = service.list_documents(space_id)
            output_dir = Path(output) if output else Path("./rendered")
            output_dir.mkdir(parents=True, exist_ok=True)

            for doc in documents:
                result = renderer.render_document(
                    content="# Placeholder",
                    document_id=doc.document_id,
                    space_id=space_id,
                )

                # Create output path
                doc_output = output_dir / doc.space_path.lstrip("/")
                doc_output = doc_output.with_suffix(".html")
                doc_output.parent.mkdir(parents=True, exist_ok=True)
                doc_output.write_text(result.content, encoding="utf-8")

            click.echo(f"Rendered {len(documents)} documents to: {output_dir}")

    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


# Sync commands
@space.command("export")
@click.argument("space_id")
@click.argument("directory", type=click.Path())
@click.option("--variant", default="by_path", help="Export variant (flat, hierarchical, by_path)")
@click.option("--no-manifest", is_flag=True, help="Skip manifest file")
def export_space(space_id: str, directory: str, variant: str, no_manifest: bool):
    """Export a space to a directory."""
    try:
        from .sync import SpaceDirectoryExporter, ExportConfig, ExportVariant

        service = get_space_service()
        space = service.get_space(space_id)

        if not space:
            click.echo(f"Space not found: {space_id}", err=True)
            raise SystemExit(1)

        documents = service.list_documents(space_id)

        def content_provider(doc_id):
            return f"# Document {doc_id}\n\nContent placeholder."

        config = ExportConfig(
            variant=ExportVariant(variant),
            include_manifest=not no_manifest,
        )

        exporter = SpaceDirectoryExporter(config)
        result = exporter.export_space(
            space=space,
            documents=documents,
            content_provider=content_provider,
            target_directory=Path(directory),
        )

        click.echo(f"Exported {result.file_count} files to: {directory}")
        if result.errors:
            for path, error in result.errors.items():
                click.echo(f"  Error: {path}: {error}", err=True)

    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("import")
@click.argument("directory", type=click.Path(exists=True))
@click.option("--space-id", "-s", help="Target space ID (creates new if not provided)")
@click.option("--name", "-n", help="Space name (for new space)")
@click.option("--conflict", default="skip", help="Conflict strategy (skip, overwrite, rename)")
def import_directory(directory: str, space_id: Optional[str], name: Optional[str], conflict: str):
    """Import a directory into a space."""
    try:
        from .sync import DirectorySpaceImporter, ImportConfig

        service = get_space_service()

        # Create or get space
        if space_id:
            space = service.get_space(space_id)
            if not space:
                click.echo(f"Space not found: {space_id}", err=True)
                raise SystemExit(1)
        else:
            space_name = name or Path(directory).name
            space = service.create_space(name=space_name)
            click.echo(f"Created space: {space.name} (ID: {space.id})")

        config = ImportConfig(conflict_strategy=conflict)
        importer = DirectorySpaceImporter(config)

        result = importer.import_directory(Path(directory))

        click.echo(f"Imported {result.document_count} documents")
        if result.conflicts:
            click.echo(f"Conflicts: {len(result.conflicts)}")
        if result.errors:
            for path, error in result.errors.items():
                click.echo(f"  Error: {path}: {error}", err=True)

    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


@space.command("sync")
@click.argument("space_id")
@click.argument("directory", type=click.Path())
@click.option("--direction", default="bidirectional", help="Sync direction")
@click.option("--conflict", default="newer_wins", help="Conflict resolution strategy")
@click.option("--dry-run", is_flag=True, help="Preview changes without applying")
def sync_space(space_id: str, directory: str, direction: str, conflict: str, dry_run: bool):
    """Sync a space with a directory."""
    try:
        from .sync import (
            BidirectionalSyncCoordinator,
            SyncConfig,
            SyncDirection,
            ConflictResolution,
        )

        service = get_space_service()
        space = service.get_space(space_id)

        if not space:
            click.echo(f"Space not found: {space_id}", err=True)
            raise SystemExit(1)

        documents = service.list_documents(space_id)

        def content_provider(doc_id):
            return f"# Document {doc_id}\n\nContent placeholder."

        config = SyncConfig(
            direction=SyncDirection(direction),
            conflict_resolution=ConflictResolution(conflict),
            dry_run=dry_run,
        )

        coordinator = BidirectionalSyncCoordinator(config)
        result = coordinator.sync(
            space=space,
            documents=documents,
            content_provider=content_provider,
            directory=Path(directory),
        )

        if dry_run:
            click.echo("Dry run - no changes applied")
            click.echo(f"Would create: {result.created_count}")
            click.echo(f"Would update: {result.updated_count}")
            click.echo(f"Would delete: {result.deleted_count}")
        else:
            click.echo(f"Sync complete:")
            click.echo(f"  Created: {result.created_count}")
            click.echo(f"  Updated: {result.updated_count}")
            click.echo(f"  Deleted: {result.deleted_count}")

        if result.conflicts:
            click.echo(f"  Conflicts: {len(result.conflicts)}")
            for c in result.conflicts[:5]:
                click.echo(f"    - {c.path}: {c.winner}")

    except SystemExit:
        raise
    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)


# Cache commands
@space.command("invalidate-cache")
@click.argument("space_id")
@click.option("--document", "-d", help="Invalidate specific document")
def invalidate_cache(space_id: str, document: Optional[str]):
    """Invalidate render cache for a space."""
    try:
        from .rendering import SpaceRenderingService

        service = SpaceRenderingService()

        if document:
            invalidated = service.invalidate_document(document, space_id)
            click.echo(f"Invalidated cache for: {document}")
        else:
            count = service.invalidate_space(space_id)
            click.echo(f"Invalidated {count} cached entries")

    except Exception as e:
        click.echo(f"Error: {e}", err=True)
        raise SystemExit(1)
