# main.py
"""
Orion FastAPI Application

Multi-tenant e-commerce marketplace platform with:
- Three deployment modes (subdomain, custom domain, path-based)
- Three interfaces (platform administration (admin), store dashboard (store), customer shop (shop))
- Comprehensive exception handling
- Middleware stack for context injection
"""

import io
import sys

# Fix Windows console encoding issues (must be at the very top)
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")

import logging
from datetime import UTC, datetime
from pathlib import Path

import sentry_sdk
from fastapi import Depends, FastAPI, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sqlalchemy import text
from sqlalchemy.orm import Session
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

from app.api.main import api_router
from app.core.config import settings

# =============================================================================
# SENTRY INITIALIZATION
# =============================================================================
# Initialize Sentry for error tracking (only if DSN is configured)
if settings.sentry_dsn:
    sentry_sdk.init(
        dsn=settings.sentry_dsn,
        environment=settings.sentry_environment,
        traces_sample_rate=settings.sentry_traces_sample_rate,
        integrations=[
            FastApiIntegration(transaction_style="endpoint"),
            SqlalchemyIntegration(),
        ],
        # Send PII data (emails, usernames) - set to False if privacy is critical
        send_default_pii=True,
        # Release version for tracking deployments
        release=f"orion@{settings.version}",
    )
    logging.getLogger(__name__).info(
        f"Sentry initialized for environment: {settings.sentry_environment}"
    )
from app.core.database import get_db
from app.core.lifespan import lifespan
from app.exceptions import ServiceUnavailableException
from app.exceptions.handler import setup_exception_handlers

# Module route auto-discovery - all page routes now come from modules
from app.modules.routes import (
    get_admin_page_routes,
    get_merchant_page_routes,
    get_platform_page_routes,
    get_store_page_routes,
    get_storefront_page_routes,
)
from middleware.frontend_type import FrontendTypeMiddleware
from middleware.language import LanguageMiddleware
from middleware.logging import LoggingMiddleware

# Import REFACTORED class-based middleware
from middleware.platform_context import PlatformContextMiddleware
from middleware.security_headers import SecurityHeadersMiddleware
from middleware.store_context import StoreContextMiddleware
from middleware.storefront_access import StorefrontAccessMiddleware
from middleware.theme_context import ThemeContextMiddleware

logger = logging.getLogger(__name__)

# Get the project root directory (where main.py is located)
BASE_DIR = Path(__file__).resolve().parent
STATIC_DIR = BASE_DIR / "static"
TEMPLATES_DIR = BASE_DIR / "app" / "templates"

# FastAPI app with lifespan
app = FastAPI(
    title=settings.project_name,
    description=settings.description,
    version=settings.version,
    lifespan=lifespan,
)

# Configure Jinja2 Templates (shared instance with i18n globals)
from app.templates_config import templates  # noqa: F401 - re-exported for compatibility

# Setup custom exception handlers (unified approach)
setup_exception_handlers(app)

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

# ============================================================================
# MIDDLEWARE REGISTRATION (CORRECTED ORDER!)
# ============================================================================
#
# IMPORTANT: Middleware execution order with BaseHTTPMiddleware:
#
# When using app.add_middleware or wrapping with BaseHTTPMiddleware,
# the LAST added middleware runs FIRST (LIFO - Last In, First Out).
#
# So we add them in REVERSE order of desired execution:
#
# Desired execution order:
#   0. ProxyHeadersMiddleware        (trust X-Forwarded-Proto from Caddy)
#   1. PlatformContextMiddleware     (detect platform from domain/path)
#   2. StoreContextMiddleware        (detect store, uses platform_clean_path)
#   3. FrontendTypeMiddleware        (detect frontend type using FrontendDetector)
#   4. LanguageMiddleware            (detect language based on frontend type)
#   5. StorefrontAccessMiddleware    (block unsubscribed storefronts)
#   6. ThemeContextMiddleware        (load theme)
#   7. LoggingMiddleware             (log all requests)
#
# Therefore we add them in REVERSE:
#   - Add ThemeContextMiddleware FIRST (runs LAST in request)
#   - Add StorefrontAccessMiddleware SECOND (runs after Language)
#   - Add LanguageMiddleware THIRD
#   - Add FrontendTypeMiddleware FOURTH
#   - Add StoreContextMiddleware FIFTH
#   - Add PlatformContextMiddleware SIXTH
#   - Add LoggingMiddleware LAST (runs FIRST for timing)
# ============================================================================

logger.info("=" * 80)
logger.info("MIDDLEWARE REGISTRATION")
logger.info("=" * 80)

# Add security headers middleware (runs early in response chain)
logger.info("Adding SecurityHeadersMiddleware (security response headers)")
app.add_middleware(SecurityHeadersMiddleware)

# Add logging middleware (runs first for timing, logs all requests/responses)
logger.info("Adding LoggingMiddleware (runs first for request timing)")
app.add_middleware(LoggingMiddleware)

# Add theme context middleware (runs last in request chain)
logger.info("Adding ThemeContextMiddleware (detects and loads theme)")
app.add_middleware(ThemeContextMiddleware)

# Add storefront access guard (blocks unsubscribed storefronts)
logger.info("Adding StorefrontAccessMiddleware (subscription gate for storefronts)")
app.add_middleware(StorefrontAccessMiddleware)

# Add language middleware (detects language after context is determined)
logger.info("Adding LanguageMiddleware (detects language based on context)")
app.add_middleware(LanguageMiddleware)

# Add frontend type detection middleware (runs after store context extraction)
logger.info("Adding FrontendTypeMiddleware (detects frontend type using FrontendDetector)")
app.add_middleware(FrontendTypeMiddleware)

# Add store context middleware (runs after platform context)
logger.info("Adding StoreContextMiddleware (detects store, uses platform_clean_path)")
app.add_middleware(StoreContextMiddleware)

# Add platform context middleware (runs first in request chain, before store)
logger.info("Adding PlatformContextMiddleware (detects platform from domain/path)")
app.add_middleware(PlatformContextMiddleware)

# Add proxy headers middleware (runs before all other middleware)
# Caddy proxies HTTPS → HTTP internally; this reads X-Forwarded-Proto
# so request.scheme = "https" and url_for() generates correct URLs
logger.info("Adding ProxyHeadersMiddleware (trust X-Forwarded-Proto from Caddy)")
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["*"])

logger.info("=" * 80)
logger.info("MIDDLEWARE ORDER SUMMARY:")
logger.info("  Execution order (request →):")
logger.info("    0. ProxyHeadersMiddleware (proxy headers)")
logger.info("    1. LoggingMiddleware (timing)")
logger.info("    2. PlatformContextMiddleware (platform detection)")
logger.info("    3. StoreContextMiddleware (store detection)")
logger.info("    4. FrontendTypeMiddleware (frontend type detection)")
logger.info("    5. LanguageMiddleware (language detection)")
logger.info("    6. StorefrontAccessMiddleware (subscription gate)")
logger.info("    7. ThemeContextMiddleware (theme loading)")
logger.info("    8. FastAPI Router")
logger.info("=" * 80)

# ========================================
# MOUNT STATIC FILES - Use absolute path
# ========================================
# NOTE: Mount order matters in FastAPI - more specific paths must come FIRST
# Module static files must be mounted before the main /static mount

# Mount module static files FIRST (self-contained modules)
# NOTE: More specific paths must be mounted BEFORE less specific paths
MODULES_DIR = BASE_DIR / "app" / "modules"
if MODULES_DIR.exists():
    for module_dir in sorted(MODULES_DIR.iterdir()):
        if not module_dir.is_dir():
            continue

        module_name = module_dir.name

        # Mount module locale files FIRST (more specific path)
        # Must come before static mount to avoid being intercepted
        module_locales = module_dir / "locales"
        if module_locales.exists():
            mount_path = f"/static/modules/{module_name}/locales"
            app.mount(mount_path, StaticFiles(directory=str(module_locales)), name=f"{module_name}_locales")
            logger.info(f"Mounted module locales: {mount_path} -> {module_locales}")

        # Mount module static files (less specific path)
        module_static = module_dir / "static"
        if module_static.exists():
            mount_path = f"/static/modules/{module_name}"
            app.mount(mount_path, StaticFiles(directory=str(module_static)), name=f"{module_name}_static")
            logger.info(f"Mounted module static files: {mount_path} -> {module_static}")

# Mount main static directory AFTER module statics
if STATIC_DIR.exists():
    app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
    logger.info(f"Mounted static files from: {STATIC_DIR}")
else:
    logger.warning(f"Static directory not found at {STATIC_DIR}")

# Mount uploads directory for user-uploaded media files
UPLOADS_DIR = BASE_DIR / "uploads"
if UPLOADS_DIR.exists():
    app.mount("/uploads", StaticFiles(directory=str(UPLOADS_DIR)), name="uploads")
    logger.info(f"Mounted uploads from: {UPLOADS_DIR}")
else:
    # Create uploads directory if it doesn't exist
    UPLOADS_DIR.mkdir(parents=True, exist_ok=True)
    app.mount("/uploads", StaticFiles(directory=str(UPLOADS_DIR)), name="uploads")
    logger.info(f"Created and mounted uploads directory: {UPLOADS_DIR}")
# ========================================

# Include API router (JSON endpoints at /api/*)
app.include_router(api_router, prefix="/api")

# Include observability endpoints (/metrics, /health/live, /health/ready, /health/tools)
from app.core.observability import health_router

app.include_router(health_router)

# ============================================================================
# FAVICON ROUTES (Must be registered BEFORE page routers)
# ============================================================================


def serve_favicon() -> Response:
    """
    Serve favicon with caching headers.
    Checks multiple possible locations for the favicon.
    """
    possible_paths = [
        STATIC_DIR / "favicon.ico",
        STATIC_DIR / "images" / "favicon.ico",
        STATIC_DIR / "assets" / "favicon.ico",
    ]

    for favicon_path in possible_paths:
        if favicon_path.exists():
            return FileResponse(
                favicon_path,
                media_type="image/x-icon",
                headers={
                    "Cache-Control": "public, max-age=86400",  # Cache for 1 day
                },
            )

    return Response(status_code=204)


@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
    """Serve favicon from root path."""
    return serve_favicon()


@app.get("/store/favicon.ico", include_in_schema=False)
async def store_favicon():
    """Handle store-prefixed favicon requests."""
    return serve_favicon()


# ============================================================================
# HEALTH CHECK (Must be before catch-all routes)
# ============================================================================


@app.get("/health")
def health_check(db: Session = Depends(get_db)):
    """Health check endpoint"""
    try:
        # Test database connection
        from app.core.build_info import get_build_info
        build = get_build_info()
        db.execute(text("SELECT 1"))
        return {
            "status": "healthy",
            "timestamp": datetime.now(UTC),
            "version": settings.version,
            "commit": build["commit"],
            "deployed_at": build["deployed_at"],
            "message": f"{settings.project_name} v{settings.version}",
            "docs": {
                "swagger": "/docs",
                "redoc": "/redoc",
                "openapi": "/openapi.json",
                "complete": "/documentation",
            },
            "features": [
                "Multi-tenant architecture with store isolation",
                "JWT Authentication with role-based access control",
                "Marketplace product import and curation",
                "Store catalog management",
                "Product-based inventory tracking",
                "Stripe Connect payment processing",
            ],
            "supported_marketplaces": [
                "Letzshop",
            ],
            "deployment_modes": [
                "Subdomain-based (production): store.platform.com",
                "Custom domain (production): customstoredomain.com",
                "Path-based (development): /stores/storename/ or /store/storename/",
            ],
            "auth_required": "Most endpoints require Bearer token authentication",
        }
    except Exception as e:
        logger.error(f"Health check failed: {e}")
        raise ServiceUnavailableException("Service unhealthy")


# ============================================================================
# HTML PAGE ROUTES (Jinja2 Templates) - AUTO-DISCOVERED FROM MODULES
# ============================================================================
# All page routes are now auto-discovered from self-contained modules.
# Routes are discovered from app/modules/*/routes/pages/{admin,store,public,storefront}.py

logger.info("=" * 80)
logger.info("ROUTE REGISTRATION (AUTO-DISCOVERY)")
logger.info("=" * 80)

# =============================================================================
# EXPLICIT REDIRECTS for paths that clash with the CMS /{slug} catch-all
# =============================================================================
# The CMS catch-all /{slug} at root level intercepts /merchants, /admin, etc.
# (without trailing slash) before FastAPI can redirect to the prefixed routers.
# These explicit routes ensure clean URLs work.


@app.get("/merchants", include_in_schema=False)
async def merchants_redirect():
    return RedirectResponse(url="/merchants/", status_code=301)


@app.get("/admin", include_in_schema=False)
async def admin_redirect():
    return RedirectResponse(url="/admin/", status_code=301)


# =============================================================================
# PLATFORM PAGES (Marketing pages - homepage, pricing, signup, etc.)
# =============================================================================
# Platform pages are served at root level (/) for platform marketing
logger.info("Auto-discovering platform (marketing) page routes...")
platform_page_routes = get_platform_page_routes()
logger.info(f"  Found {len(platform_page_routes)} platform page route modules")

for route_info in platform_page_routes:
    logger.info(f"  Registering {route_info.module_code} platform pages (priority={route_info.priority})")
    app.include_router(
        route_info.router,
        prefix="",  # Platform pages at root
        tags=route_info.tags,
        include_in_schema=route_info.include_in_schema,
    )

# =============================================================================
# ADMIN PAGES
# =============================================================================
logger.info("Auto-discovering admin page routes...")
admin_page_routes = get_admin_page_routes()
logger.info(f"  Found {len(admin_page_routes)} admin page route modules")

for route_info in admin_page_routes:
    prefix = f"/admin{route_info.custom_prefix}" if route_info.custom_prefix else "/admin"
    logger.info(f"  Registering {route_info.module_code} admin pages at {prefix}")
    app.include_router(
        route_info.router,
        prefix=prefix,
        tags=route_info.tags,
        include_in_schema=route_info.include_in_schema,
    )

# =============================================================================
# MERCHANT PAGES (Merchant billing portal)
# =============================================================================
logger.info("Auto-discovering merchant page routes...")
merchant_page_routes = get_merchant_page_routes()
logger.info(f"  Found {len(merchant_page_routes)} merchant page route modules")

for route_info in merchant_page_routes:
    prefix = f"/merchants{route_info.custom_prefix}" if route_info.custom_prefix else "/merchants"
    logger.info(f"  Registering {route_info.module_code} merchant pages at {prefix}")
    app.include_router(
        route_info.router,
        prefix=prefix,
        tags=route_info.tags,
        include_in_schema=route_info.include_in_schema,
    )

# =============================================================================
# STORE PAGES
# =============================================================================
# Store dashboard pages - Register at TWO prefixes:
# 1. /store/* (for prod: subdomain/custom domain, after path rewrite by middleware)
# 2. /store/{store_code}/* (for dev: path-based, after /platforms/{code}/ strip)
logger.info("Auto-discovering store page routes...")
store_page_routes = get_store_page_routes()
logger.info(f"  Found {len(store_page_routes)} store page route modules")

# Mount 1: /store/* (production subdomain/custom domain — store resolved by middleware)
logger.info("  Registering store routes at /store/*")
for route_info in store_page_routes:
    prefix = f"/store{route_info.custom_prefix}" if route_info.custom_prefix else "/store"
    logger.info(f"    - {route_info.module_code} at {prefix} (priority={route_info.priority})")
    app.include_router(
        route_info.router,
        prefix=prefix,
        tags=route_info.tags,
        include_in_schema=route_info.include_in_schema,
    )

# Mount 2: /store/{store_code}/* (dev path-based + production path-based demo/trial)
logger.info("  Registering store routes at /store/{store_code}/*")
for route_info in store_page_routes:
    prefix = f"/store/{{store_code}}{route_info.custom_prefix}" if route_info.custom_prefix else "/store/{store_code}"
    app.include_router(
        route_info.router,
        prefix=prefix,
        tags=route_info.tags,
        include_in_schema=False,
    )

# =============================================================================
# STOREFRONT PAGES (Customer Shop)
# =============================================================================
# Customer shop pages - Register at TWO prefixes:
# 1. /storefront/* (for prod: subdomain/custom domain, after path rewrite by middleware)
# 2. /storefront/{store_code}/* (for dev: path-based, after /platforms/{code}/ strip)
#
# Non-core module routes get a module-gate dependency injected automatically
# so disabled modules return 404 without any hardcoded path map.
logger.info("Auto-discovering storefront page routes...")
storefront_page_routes = get_storefront_page_routes()
logger.info(f"  Found {len(storefront_page_routes)} storefront page route modules")


def _storefront_deps_for(route_info):
    """Build dependency list for a storefront route — gates non-core modules."""
    from app.api.deps import make_storefront_module_gate
    from app.modules.registry import MODULES

    code = route_info.module_code
    if not code:
        return []
    module_def = MODULES.get(code)
    if module_def and module_def.is_core:
        return []
    return [Depends(make_storefront_module_gate(code))]


# Register at /storefront/* (prod mode — middleware rewrites /products → /storefront/products)
logger.info("  Registering storefront routes at /storefront/*")
for route_info in storefront_page_routes:
    prefix = f"/storefront{route_info.custom_prefix}" if route_info.custom_prefix else "/storefront"
    logger.info(f"    - {route_info.module_code} at {prefix} (priority={route_info.priority})")
    app.include_router(
        route_info.router,
        prefix=prefix,
        tags=["storefront-pages"],
        include_in_schema=False,
        dependencies=_storefront_deps_for(route_info),
    )

# Register at /storefront/{store_code}/* (dev mode — /platforms/oms/storefront/WIZATECH/...)
logger.info("  Registering storefront routes at /storefront/{store_code}/*")
for route_info in storefront_page_routes:
    prefix = f"/storefront/{{store_code}}{route_info.custom_prefix}" if route_info.custom_prefix else "/storefront/{store_code}"
    app.include_router(
        route_info.router,
        prefix=prefix,
        tags=["storefront-pages"],
        include_in_schema=False,
        dependencies=_storefront_deps_for(route_info),
    )


# =============================================================================
# HOSTING PUBLIC PAGES (POC preview)
# =============================================================================
try:
    from app.modules.hosting.routes.pages.public import router as hosting_public_router

    app.include_router(
        hosting_public_router,
        prefix="",
        tags=["hosting-public"],
        include_in_schema=False,
    )
    logger.info("Registered hosting public page routes")
except ImportError:
    pass  # Hosting module not installed


# ============================================================================
# PLATFORM ROUTING (via PlatformContextMiddleware)
#
# The PlatformContextMiddleware handles all platform routing by rewriting paths:
# - /platforms/oms/            → rewrites to / (served by platform_pages router)
# - /platforms/oms/pricing     → rewrites to /pricing (served by platform_pages router)
# - /platforms/loyalty/        → rewrites to / (served by platform_pages router)
#
# The middleware also sets request.state.platform so handlers know which
# platform's content to serve. All platform page routes are defined in
# app/routes/platform_pages.py which is included above.
#
# URL Structure (Development - localhost:9999):
# - /                              → Main marketing site ('main' platform)
# - /about                         → Main marketing site about page
# - /platforms/oms/                → OMS platform homepage
# - /platforms/oms/pricing         → OMS platform pricing page
# - /platforms/loyalty/            → Loyalty platform homepage
#
# URL Structure (Production - domain-based):
# - orion.lu/                   → Main marketing site
# - omsflow.lu/                        → OMS platform homepage
# - rewardflow.lu/                    → Loyalty platform homepage
# ============================================================================


logger.info("=" * 80)

# Log all registered routes
logger.info("=" * 80)
logger.info("REGISTERED ROUTES SUMMARY")
logger.info("=" * 80)
for route in app.routes:
    if hasattr(route, "methods") and hasattr(route, "path"):
        methods = ", ".join(route.methods) if route.methods else "N/A"
        logger.info(f"  {methods:<10} {route.path:<60}")
logger.info("=" * 80)

@app.get("/documentation", response_class=HTMLResponse, include_in_schema=False)
async def documentation():
    """Redirect to documentation"""
    if settings.debug:
        return RedirectResponse(url="http://localhost:8001")
    return RedirectResponse(url=settings.documentation_url)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
