"""
RelationalAI CLI - Tools for working with semantic models.
For more documentation on these commands, visit: https://docs.relationalai.com
"""

from __future__ import annotations

import sys
from pathlib import Path

import click

from rich.console import Console
from rich.table import Table

from .config_template import CONFIG_TEMPLATE

console = Console()

def _load_config():
    """Load config using the unified Config() factory (rai yaml → snowflake toml → dbt profiles)."""
    from relationalai.config import Config

    try:
        return Config()
    except Exception as e:
        raise click.ClickException(f"Failed to load config: {e}") from e


def _validate_session(session) -> None:  # noqa: ANN001
    """Best-effort validation across session types."""
    if hasattr(session, "sql"):
        session.sql("select 1").collect()
        return
    if hasattr(session, "execute"):
        session.execute("select 1").fetchall()
        return


@click.group(invoke_without_command=True)
@click.version_option()
@click.pass_context
def cli(ctx: click.Context):
    """RelationalAI CLI - Tools for working with semantic models. For more documentation on these commands, visit: https://docs.relationalai.com"""
    if ctx.invoked_subcommand is None:
        # Show help if no command provided
        click.echo(ctx.get_help())


@cli.command()  # type: ignore[attr-defined]
def analyze():
    """Check for errors, warnings, and performance issues in the models."""
    console.print("analyze")


@cli.command()  # type: ignore[attr-defined]
def build():
    """Create artifacts, show compilation errors, and produce SQL for the models."""
    console.print("build")


@cli.command()  # type: ignore[attr-defined]
def connect():
    """Validate config and database connection."""
    config = _load_config()
    try:
        connection = config.get_default_connection()
    except Exception as e:
        raise click.ClickException(f"Failed to resolve default connection: {e}") from e

    session = None
    err: Exception | None = None
    try:
        session = connection.get_session()
        _validate_session(session)
        status = "[green]OK[/green]"
    except Exception as e:
        session = None
        err = e
        status = "[red]FAIL[/red]"

    table = Table(show_header=False)
    table.add_row("Connection Status", status)
    console.print(table)

    if err is not None:
        raise click.ClickException(f"Connection failed: {err}") from err

    return session


@cli.command()  # type: ignore[attr-defined]
def deploy():
    """Deploy models/views to the database and produce SQL for execution."""
    console.print("deploy")


@cli.command()  # type: ignore[attr-defined]
def explore():
    """Model explorer for visualization of data."""
    console.print("explore")


@cli.command()  # type: ignore[attr-defined]
def init():
    """Create a template for the YAML config file."""
    config_file = Path("raiconfig.yml")

    if config_file.exists():
        console.print(f"[yellow]Config file '{config_file}' already exists. Skipping creation.[/yellow]")
        return

    try:
        config_file.write_text(CONFIG_TEMPLATE, encoding="utf-8")
        console.print(f"[green]✓ Created config file '{config_file}'[/green]")
    except Exception as e:
        console.print(f"[red]✗ Failed to create config file: {e}[/red]")
        raise click.ClickException(f"Failed to create config file: {e}")


@cli.command()  # type: ignore[attr-defined]
@click.option("--uninstall", is_flag=True, help="Nukes the database")
def clean(uninstall: bool):
    """Clean up build folder and remove artifacts."""
    console.print("clean")


@cli.command()  # type: ignore[attr-defined]
def test():
    """Run constraints and tests."""
    console.print("test")


@cli.group()  # type: ignore[attr-defined]
def docs():
    """Generate and serve documentation site."""
    pass


@docs.command()
@click.option(
    "--output",
    "-o",
    default=None,
    help="Output directory for generated documentation (defaults to docs/dist)",
)
def generate(output: str | None):
    """Generate static documentation site from models."""
    from .docs import generate_docs

    try:
        generate_docs(output_dir=output)
    except click.ClickException as e:
        # Print formatted error message and exit
        console.print(f"[red]✗ Failed to generate documentation: {e.message}[/red]")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]✗ Failed to generate documentation: {e}[/red]")
        sys.exit(1)


@docs.command()
@click.option(
    "--port",
    "-p",
    default=8000,
    type=int,
    help="Port to serve documentation on (default: 8000)",
)
@click.option(
    "--host",
    default="127.0.0.1",
    help="Host to bind to (default: 127.0.0.1)",
)
@click.option(
    "--build-dir",
    default=None,
    help="Directory containing built documentation (defaults to docs/dist)",
)
@click.option(
    "--no-open",
    is_flag=True,
    help="Don't open browser automatically",
)
def serve(port: int, host: str, build_dir: str | None, no_open: bool):
    """Serve generated documentation site."""
    from .docs import find_docs_dir, serve_docs

    # Determine build directory
    if build_dir is None:
        docs_dir = find_docs_dir()
        build_dir = str(docs_dir / "dist")

    try:
        serve_docs(
            build_dir=build_dir,
            host=host,
            port=port,
            open_browser=not no_open,
        )
    except click.ClickException as e:
        # Print formatted error message and exit
        console.print(f"[red]✗ Failed to start server: {e.message}[/red]")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]✗ Failed to start server: {e}[/red]")
        sys.exit(1)


@docs.command()
@click.option(
    "--port",
    "-p",
    default=5173,
    type=int,
    help="Port to serve development server on (default: 5173)",
)
@click.option(
    "--host",
    default="127.0.0.1",
    help="Host to bind to (default: 127.0.0.1)",
)
@click.option(
    "--no-open",
    is_flag=True,
    help="Don't open browser automatically",
)
def dev(port: int, host: str, no_open: bool):
    """Start development server with hot module replacement (HMR).

    This command runs Vite's dev server which provides:
    - Hot module replacement - changes reflect instantly
    - Fast refresh - no need to rebuild on every change
    - Source maps for debugging

    Use this for development instead of 'rai docs generate' + 'rai docs serve'.
    """
    from .docs import dev_docs

    try:
        dev_docs(
            host=host,
            port=port,
            open_browser=not no_open,
        )
    except click.ClickException as e:
        # Print formatted error message and exit
        console.print(f"[red]✗ Failed to start dev server: {e.message}[/red]")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]✗ Failed to start dev server: {e}[/red]")
        sys.exit(1)


@docs.command(name="clean")
def clean_docs():
    """Remove auto generated files and directories."""
    from .docs import cleanup_docs

    try:
        cleanup_docs()
    except click.ClickException as e:
        # Print formatted error message and exit
        console.print(f"[red]✗ Failed to cleanup: {e.message}[/red]")
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]✗ Failed to cleanup: {e}[/red]")
        sys.exit(1)


if __name__ == "__main__":
    cli()  # type: ignore[call-arg]
