"""FastAPI application initialization and configuration."""
from __future__ import annotations

import asyncio
import mimetypes
import os
import time
import warnings
from pathlib import Path
from datetime import datetime

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles

from searchat.core.watcher import ConversationWatcher
from searchat.core.logging_config import setup_logging, get_logger
from searchat.core.progress import LoggingProgressAdapter
from searchat.api.dependencies import (
    initialize_services,
    start_background_warmup,
    get_config,
    get_indexer,
    get_search_engine,
    get_watcher,
    set_watcher,
    watcher_stats,
    indexing_state,
)
import searchat.api.dependencies as deps
from searchat.api.readiness import get_readiness
from searchat.api.routers import (
    search_router,
    conversations_router,
    bookmarks_router,
    stats_router,
    indexing_router,
    backup_router,
    admin_router,
    status_router,
    chat_router,
    queries_router,
    code_router,
    docs_router,
    dashboards_router,
)
from searchat.config.constants import (
    DEFAULT_HOST,
    DEFAULT_PORT,
    PORT_SCAN_RANGE,
    ENV_PORT,
    ENV_HOST,
    ERROR_INVALID_PORT,
    ERROR_PORT_IN_USE,
)

warnings.filterwarnings(
    "ignore",
    category=UserWarning,
    module=r"multiprocessing\.resource_tracker",
)

_warn_filter = "ignore::UserWarning:multiprocessing.resource_tracker"
_existing_warn = os.environ.get("PYTHONWARNINGS", "")
if _warn_filter not in _existing_warn:
    os.environ["PYTHONWARNINGS"] = (
        f"{_existing_warn},{_warn_filter}" if _existing_warn else _warn_filter
)

# Ensure correct JS MIME types across platforms (notably Windows).
# Without this, browsers may refuse to load ES modules from /static.
mimetypes.add_type("text/javascript", ".js")
mimetypes.add_type("text/javascript", ".mjs")


# Cache HTML at module load for faster responses
_HTML_PATH = Path(__file__).parent.parent / "web" / "index.html"
_CACHED_HTML = _HTML_PATH.read_text(encoding='utf-8')


# Create FastAPI app
app = FastAPI(
    title="Searchat API",
    description="Semantic search for AI coding agent conversations",
    version="0.2.0",
)

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Mount static files
static_path = Path(__file__).parent.parent / "web" / "static"
app.mount("/static", StaticFiles(directory=str(static_path)), name="static")

# Mount docs directory for infographics
docs_path = Path(__file__).parent.parent.parent.parent / "docs"
if docs_path.exists():
    app.mount("/docs", StaticFiles(directory=str(docs_path)), name="docs")

# Register routers
app.include_router(search_router, prefix="/api", tags=["search"])
app.include_router(conversations_router, prefix="/api", tags=["conversations"])
app.include_router(bookmarks_router, prefix="/api", tags=["bookmarks"])
app.include_router(stats_router, prefix="/api", tags=["statistics"])
app.include_router(indexing_router, prefix="/api", tags=["indexing"])
app.include_router(backup_router, prefix="/api/backup", tags=["backup"])
app.include_router(admin_router, prefix="/api", tags=["admin"])
app.include_router(status_router, prefix="/api", tags=["status"])
app.include_router(chat_router, prefix="/api", tags=["chat"])
app.include_router(queries_router, prefix="/api", tags=["queries"])
app.include_router(code_router, prefix="/api", tags=["code"])
app.include_router(docs_router, prefix="/api", tags=["docs"])
app.include_router(dashboards_router, prefix="/api", tags=["dashboards"])


def on_new_conversations(file_paths: list[str]) -> None:
    """Callback when watcher detects new conversation files."""
    global projects_cache, watcher_stats, indexing_state

    logger = get_logger(__name__)
    logger.info(f"Auto-indexing {len(file_paths)} new conversations")

    try:
        indexer = get_indexer()
        search_engine = get_search_engine()

        # Mark indexing in progress
        indexing_state["in_progress"] = True
        indexing_state["operation"] = "watcher"
        indexing_state["started_at"] = datetime.now().isoformat()
        indexing_state["files_total"] = len(file_paths)
        indexing_state["files_processed"] = 0

        # Use logging-based progress for background task
        progress = LoggingProgressAdapter()

        enable_adaptive = False
        try:
            enable_adaptive = bool(
                getattr(indexer, "config", None)
                and getattr(indexer.config, "indexing", None)
                and indexer.config.indexing.enable_adaptive_indexing
            )
        except Exception:
            enable_adaptive = False

        if enable_adaptive and hasattr(indexer, "index_adaptive"):
            stats = indexer.index_adaptive(file_paths, progress)
        else:
            stats = indexer.index_append_only(file_paths, progress)

        updated_conversations = getattr(stats, "updated_conversations", 0)
        if stats.new_conversations > 0 or updated_conversations > 0:
            deps.invalidate_search_index()

            watcher_stats["indexed_count"] += stats.new_conversations + updated_conversations
            watcher_stats["last_update"] = datetime.now().isoformat()

            logger.info(
                f"Indexed {stats.new_conversations} new conversations and "
                f"updated {updated_conversations} conversations "
                f"in {stats.update_time_seconds:.2f}s"
            )
    except Exception as e:
        logger.error(f"Failed to index new conversations: {e}")
    finally:
        # Mark indexing complete
        indexing_state["in_progress"] = False
        indexing_state["operation"] = None


@app.on_event("startup")
async def startup_event():
    """Initialize services and start the file watcher on server startup."""
    started = time.perf_counter()
    # IMPORTANT: Initialize services FIRST (loads config)
    initialize_services()

    # Start warmup in background (non-blocking)
    start_background_warmup()

    # THEN get config and setup logging
    config = get_config()
    setup_logging(config.logging)
    logger = get_logger(__name__)

    if os.getenv("SEARCHAT_PROFILE_STARTUP") == "1":
        elapsed_ms = (time.perf_counter() - started) * 1000.0
        logger.info("Startup: initialize_services + schedule warmup %.1fms", elapsed_ms)

    # Start file watcher in background (do not block startup).
    asyncio.create_task(_start_watcher_background(config))

    if os.getenv("SEARCHAT_PROFILE_STARTUP") == "1":
        elapsed_ms = (time.perf_counter() - started) * 1000.0
        logger.info("Startup: total startup_event %.1fms", elapsed_ms)


async def _start_watcher_background(config):
    readiness = get_readiness()
    readiness.set_watcher("starting")

    logger = get_logger(__name__)
    try:
        indexer = get_indexer()
        watcher = ConversationWatcher(
            config=config,
            on_update=on_new_conversations,
            batch_delay_seconds=5.0,
            debounce_seconds=2.0,
        )

        indexed_paths = await asyncio.to_thread(indexer.get_indexed_file_paths)
        watcher.set_indexed_files(indexed_paths)

        watcher.start()
        set_watcher(watcher)

        readiness.set_watcher("running")
        logger.info(
            f"Live watcher started, monitoring {len(watcher.get_watched_directories())} directories"
        )
    except Exception as e:
        readiness.set_watcher("error", error=str(e))
        logger.error(f"Failed to start watcher: {e}")


@app.on_event("shutdown")
async def shutdown_event():
    """Stop the file watcher on server shutdown."""
    watcher = get_watcher()
    if watcher:
        watcher.stop()
        set_watcher(None)


@app.get("/", response_class=HTMLResponse)
async def root():
    """Serve the main HTML page."""
    return HTMLResponse(_CACHED_HTML)


@app.get("/favicon.ico", include_in_schema=False)
async def favicon_ico():
    """Redirect default browser favicon request to our SVG favicon."""
    return RedirectResponse(url="/static/favicon.svg")


@app.get("/conversation/{conversation_id}", response_class=HTMLResponse)
async def serve_conversation_page(conversation_id: str):
    """Serve HTML page for viewing a specific conversation."""
    # For now, serve the same main page (it handles conversation viewing via client-side routing)
    return HTMLResponse(_CACHED_HTML)


def main():
    """Run the server with configurable host and port."""
    import uvicorn
    import socket
    import warnings
    import threading
    import time
    import webbrowser
    import sys

    prog = Path(sys.argv[0]).name
    argv = set(sys.argv[1:])
    if prog.startswith("searchat-web"):
        if "--version" in argv:
            from searchat import __version__

            print(__version__)
            return
        if "-h" in argv or "--help" in argv:
            print("Usage: searchat-web")
            print()
            print("Environment variables:")
            print(f"  {ENV_HOST}=<host>   (default: {DEFAULT_HOST})")
            print(f"  {ENV_PORT}=<port>   (default: auto-scan {PORT_SCAN_RANGE[0]}-{PORT_SCAN_RANGE[1]})")
            print("  SEARCHAT_OPEN_BROWSER=0   Disable opening the browser tab")
            print()
            return

    # Python 3.12 can emit a noisy multiprocessing.resource_tracker warning on
    # shutdown with some native deps (e.g., torch/sentence-transformers).
    warnings.filterwarnings(
        "ignore",
        message=r"resource_tracker: There appear to be .* leaked semaphore objects to clean up at shutdown",
        category=UserWarning,
    )

    # Get host from environment or use default
    host = os.getenv(ENV_HOST, DEFAULT_HOST)

    # Get port from environment or scan for available port
    env_port = os.getenv(ENV_PORT)
    if env_port:
        try:
            port = int(env_port)
            if not (1 <= port <= 65535):
                print(ERROR_INVALID_PORT.format(port=port))
                return
        except ValueError:
            print(ERROR_INVALID_PORT.format(port=env_port))
            return
    else:
        # Scan for available port in range
        port, max_port = PORT_SCAN_RANGE

        while port <= max_port:
            try:
                # Test if port is available
                with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                    s.bind((host, port))
                # Port is available
                break
            except OSError:
                port += 1

        if port > max_port:
            print(ERROR_PORT_IN_USE.format(
                start=PORT_SCAN_RANGE[0],
                end=PORT_SCAN_RANGE[1],
                port=port
            ))
            return

    print(f"Starting Searchat server...")
    print(f"  URL: http://localhost:{port}")
    print(f"  Host: {host}")
    print(f"  Port: {port}")
    print()
    print("Press Ctrl+C to stop")

    open_browser_raw = os.getenv("SEARCHAT_OPEN_BROWSER", "1").strip().lower()
    open_browser = open_browser_raw not in {"0", "false", "no", "off"}
    if "--no-browser" in argv:
        open_browser = False
    interactive = sys.stdout.isatty()

    def _open_browser_when_ready() -> None:
        url_host = host
        if host in {"0.0.0.0", "::", ""}:
            url_host = "localhost"
        url = f"http://{url_host}:{port}"

        probe_host = "127.0.0.1" if url_host == "localhost" else url_host

        deadline = time.time() + 10.0
        while time.time() < deadline:
            try:
                with socket.create_connection((probe_host, port), timeout=0.25):
                    break
            except OSError:
                time.sleep(0.1)
        else:
            print(f"Warning: server did not start within 10s; not opening browser ({url})")
            return

        try:
            webbrowser.open_new_tab(url)
        except Exception as exc:
            print(f"Warning: failed to open browser tab: {exc}")

    if open_browser and interactive:
        threading.Thread(target=_open_browser_when_ready, daemon=True).start()

    uvicorn.run(app, host=host, port=port)


if __name__ == "__main__":
    main()
