"""
Databrowse Monitor - Test-oriented data navigation.

Navigate users, entities, and data to find the right test scenario.
Room-agnostic: configure via environment or cfg/<room>/databrowse/depot/

Run standalone:
    python main.py

Or use uvicorn:
    uvicorn main:app --port 12020 --reload
"""

import json
import os
from pathlib import Path
from typing import Any, Dict, List, Optional

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy import create_engine, text
from sqlalchemy.engine import Engine

app = FastAPI(title="Databrowse Monitor", version="0.1.0")

# Paths
BASE_DIR = Path(__file__).parent
DEPOT_DIR = Path(os.getenv("DATABROWSE_DEPOT", str(BASE_DIR / "depot")))
templates = Jinja2Templates(directory=str(BASE_DIR))

# =============================================================================
# ROOM CONFIG - Pluggable environment targeting
# =============================================================================
# Default room: local development database
# Override with env vars or future room selector UI

ROOM_CONFIG = {
    "name": os.getenv("ROOM_NAME", "local"),
    "db": {
        "host": os.getenv("DB_HOST", "localhost"),
        "port": os.getenv("DB_PORT", "5432"),
        "name": os.getenv("DB_NAME", "database"),
        "user": os.getenv("DB_USER", "user"),
        "password": os.getenv("DB_PASSWORD", ""),
    },
}


def get_db_url() -> str:
    """Build database URL from room config."""
    db = ROOM_CONFIG["db"]
    return f"postgresql://{db['user']}:{db['password']}@{db['host']}:{db['port']}/{db['name']}"


# =============================================================================
# DEPOT DATA - Load from JSON files
# =============================================================================

_schema: Optional[Dict[str, Any]] = None
_views: Optional[Dict[str, Any]] = None
_scenarios: Optional[Dict[str, Any]] = None


def load_depot_file(filename: str) -> Dict[str, Any]:
    """Load JSON file from depot directory."""
    path = DEPOT_DIR / filename
    if not path.exists():
        return {}
    with open(path) as f:
        return json.load(f)


def get_schema() -> Dict[str, Any]:
    """Get data model schema."""
    global _schema
    if _schema is None:
        _schema = load_depot_file("schema.json")
    return _schema


def get_views() -> Dict[str, Any]:
    """Get view configurations."""
    global _views
    if _views is None:
        _views = load_depot_file("views.json")
    return _views


def get_scenarios() -> Dict[str, Any]:
    """Get test scenario definitions."""
    global _scenarios
    if _scenarios is None:
        _scenarios = load_depot_file("scenarios.json")
    return _scenarios


# =============================================================================
# DATABASE
# =============================================================================

_engine: Optional[Engine] = None


def get_engine() -> Optional[Engine]:
    """Get or create database engine (lazy singleton)."""
    global _engine
    if _engine is None:
        try:
            _engine = create_engine(get_db_url(), pool_pre_ping=True)
        except Exception as e:
            print(f"[databrowse] DB engine error: {e}")
    return _engine


# =============================================================================
# SQL MODE - Direct database queries
# =============================================================================


def build_view_query(view_name: str) -> Optional[str]:
    """Build SQL query from view configuration.

    This is the SQL mode - generates queries directly from view definitions.
    """
    views = get_views()
    view = None
    for v in views.get("views", []):
        if v.get("slug") == view_name or v.get("name") == view_name:
            view = v
            break

    if not view:
        return None

    if view.get("mode") != "sql":
        return None  # Only SQL mode supported for now

    schema = get_schema()
    entity_def = schema.get("definitions", {}).get(view["entity"], {})
    table = entity_def.get("table")

    if not table:
        return None

    # Build field list
    fields = view.get("fields", [])
    field_list = []

    for field_name in fields:
        # Check if it's a regular field or computed field
        if field_name in entity_def.get("properties", {}):
            col = entity_def["properties"][field_name].get("column", field_name)
            field_list.append(f"{table}.{col} as {field_name}")
        elif field_name in entity_def.get("computed", {}):
            computed_def = entity_def["computed"][field_name]
            sql_expr = computed_def.get("sql", "NULL")
            field_list.append(f"({sql_expr}) as {field_name}")

    # Add computed group field if specified
    group_by = view.get("group_by")
    computed_group = view.get("computed_group", {}).get(group_by)
    if computed_group:
        group_sql = computed_group.get("sql")
        field_list.append(f"({group_sql}) as {group_by}")

    # Build query
    query = f"SELECT {', '.join(field_list)} FROM {table}"

    # Add filters
    filters = view.get("filter", {})
    where_clauses = []
    for field, value in filters.items():
        if isinstance(value, list):
            # IN clause
            values_str = ", ".join([f"'{v}'" for v in value])
            where_clauses.append(f"{table}.{field} IN ({values_str})")
        else:
            where_clauses.append(f"{table}.{field} = '{value}'")

    if where_clauses:
        query += " WHERE " + " AND ".join(where_clauses)

    # Add ordering
    order_by = view.get("order_by")
    if order_by:
        query += f" ORDER BY {order_by}"

    return query


def execute_view_query(view_name: str) -> List[Dict[str, Any]]:
    """Execute view query and return results."""
    engine = get_engine()
    if not engine:
        return []

    query_sql = build_view_query(view_name)
    if not query_sql:
        return []

    try:
        with engine.connect() as conn:
            result = conn.execute(text(query_sql))
            rows = result.fetchall()
            columns = result.keys()
            return [dict(zip(columns, row)) for row in rows]
    except Exception as e:
        print(f"[databrowse] Query error for view '{view_name}': {e}")
        print(f"[databrowse] Query was: {query_sql}")
        return []


def group_results_by(
    results: List[Dict[str, Any]], group_field: str, labels: Dict[str, str] = None
) -> Dict[str, Any]:
    """Group results by a field value."""
    grouped = {}
    for row in results:
        key = row.get(group_field, "unknown")
        if key not in grouped:
            grouped[key] = []
        grouped[key].append(row)

    # Add labels if provided
    if labels:
        return {
            key: {"label": labels.get(key, key), "items": items}
            for key, items in grouped.items()
        }
    return {key: {"label": key, "items": items} for key, items in grouped.items()}


# =============================================================================
# API MODE - Placeholder for backend integration
# =============================================================================


def fetch_via_api(view_name: str) -> List[Dict[str, Any]]:
    """Fetch data via backend API.

    This is the API mode - will call backend endpoints.
    Currently placeholder.
    """
    # TODO: Implement API mode
    # Will call endpoints like /api/v1/databrowse/{view_name}/
    # with auth token from room config
    return []


# =============================================================================
# ROUTES
# =============================================================================


@app.get("/health")
def health():
    """Health check."""
    engine = get_engine()
    db_ok = False
    if engine:
        try:
            with engine.connect() as conn:
                conn.execute(text("SELECT 1"))
                db_ok = True
        except:
            pass

    return {
        "status": "ok" if db_ok else "degraded",
        "service": "databrowse-monitor",
        "room": ROOM_CONFIG["name"],
        "database": "connected" if db_ok else "disconnected",
        "depot": {
            "path": str(DEPOT_DIR),
            "schema": "loaded" if get_schema() else "missing",
            "views": len(get_views().get("views", [])),
            "scenarios": len(get_scenarios().get("scenarios", [])),
        },
    }


@app.get("/", response_class=HTMLResponse)
def index(request: Request):
    """Main data browser landing page."""
    views = get_views().get("views", [])
    scenarios = get_scenarios().get("scenarios", [])

    return templates.TemplateResponse(
        "index.html",
        {
            "request": request,
            "views": views,
            "scenarios": scenarios,
            "room_name": ROOM_CONFIG["name"],
        },
    )


@app.get("/view/{view_slug}", response_class=HTMLResponse)
def view_data(request: Request, view_slug: str):
    """Display data for a specific view."""
    views_data = get_views()
    view_config = None

    for v in views_data.get("views", []):
        if v.get("slug") == view_slug:
            view_config = v
            break

    if not view_config:
        return HTMLResponse("View not found", status_code=404)

    # Execute query based on mode
    mode = view_config.get("mode", "sql")
    if mode == "sql":
        results = execute_view_query(view_slug)
    elif mode == "api":
        results = fetch_via_api(view_slug)
    else:
        results = []

    # Group results if specified
    group_by = view_config.get("group_by")
    if group_by:
        computed_group = view_config.get("computed_group", {}).get(group_by, {})
        labels = computed_group.get("labels", {})
        grouped_results = group_results_by(results, group_by, labels)
    else:
        grouped_results = {"all": {"label": "All Results", "items": results}}

    return templates.TemplateResponse(
        "view.html",
        {
            "request": request,
            "view": view_config,
            "results": results,
            "grouped_results": grouped_results,
            "total": len(results),
            "room_name": ROOM_CONFIG["name"],
        },
    )


@app.get("/api/views")
def api_views():
    """List all available views."""
    return get_views()


@app.get("/api/view/{view_slug}")
def api_view_data(view_slug: str):
    """Get data for a specific view as JSON."""
    views_data = get_views()
    view_config = None

    for v in views_data.get("views", []):
        if v.get("slug") == view_slug:
            view_config = v
            break

    if not view_config:
        return JSONResponse({"error": "View not found"}, status_code=404)

    # Execute query
    mode = view_config.get("mode", "sql")
    if mode == "sql":
        results = execute_view_query(view_slug)
    elif mode == "api":
        results = fetch_via_api(view_slug)
    else:
        results = []

    return {
        "view": view_config,
        "results": results,
        "total": len(results),
        "room": ROOM_CONFIG["name"],
    }


@app.get("/api/schema")
def api_schema():
    """Get data model schema."""
    return get_schema()


@app.get("/api/scenarios")
def api_scenarios():
    """Get test scenario definitions."""
    return get_scenarios()


# =============================================================================
# MAIN
# =============================================================================

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=int(os.getenv("PORT", "12020")),
        reload=True,
    )
