from __future__ import annotations

import os
from urllib.parse import urlparse

from fastapi import FastAPI, Request
from fastapi.exception_handlers import request_validation_exception_handler
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from jarvis_finance.api.routers import budget, cash, crypto, crypto_trader, equity, health, market, overview, positions, postfinance, reports, system, truewealth
from jarvis_finance.api.security import WRITE_METHODS, is_local_request, is_write_request_allowed, resolve_write_mode

LOCAL_ORIGINS = [
    "http://localhost:5173",
    "http://127.0.0.1:5173",
    "http://100.85.29.67:5173",
    "http://agent.tailbd371b.ts.net:5173",
    "http://localhost:8503",
    "http://127.0.0.1:8503",
]
READ_ONLY_POST_PATHS = {
    "/api/portfolio/ingestion/preview",
    "/api/portfolio/policy/preview",
    "/api/postfinance/imports/preview",
    "/api/truewealth/imports/preview",
    "/api/truewealth/manual-values/preview",
    "/api/portfolio/performance/reclassification/preview",
    "/api/portfolio/performance/truewealth-cashflows/preview",
    "/api/portfolio/performance/truewealth-bank-payments/preview",
    "/api/portfolio/performance/truewealth/activation-package-preview",
    "/api/budget/household/review/items/preview",
    "/api/budget/household/transactions/category/preview",
    "/api/market/equity/update-quotes/dry-run",
    "/api/crypto/reconciliation/snapshots/preview",
    "/api/crypto/reconciliation/transfers/preview",
}
POSTFINANCE_UPLOAD_PATHS = {
    "/api/postfinance/imports/preview",
    "/api/postfinance/imports/confirm",
}
MAX_POSTFINANCE_REQUEST_BYTES = 84_000_000


def _is_safe_local_origin(origin: str) -> bool:
    parsed = urlparse(origin)
    if parsed.scheme != "http" or not parsed.hostname or not parsed.port:
        return False
    hostname = parsed.hostname.lower()
    if hostname in {"localhost", "127.0.0.1"}:
        return True
    if hostname.startswith("100."):
        return True
    if hostname.endswith(".ts.net"):
        return True
    return False


def build_local_origins(environ: dict[str, str] | None = None) -> list[str]:
    env = environ or os.environ
    origins = list(LOCAL_ORIGINS)
    for raw_origin in env.get("JARVIS_FINANCE_CORS_ORIGINS", "").split(","):
        origin = raw_origin.strip().rstrip("/")
        if origin and _is_safe_local_origin(origin) and origin not in origins:
            origins.append(origin)
    return origins


def create_app(*, write_mode: str | None = None) -> FastAPI:
    active_write_mode = resolve_write_mode(write_mode)
    app = FastAPI(
        title="JARVIS Finance API",
        version="0.1.0",
        description="Read-only FastAPI v0 skeleton for the future Vue User Dashboard.",
    )
    app.add_middleware(
        CORSMiddleware,
        allow_origins=build_local_origins(),
        allow_credentials=False,
        allow_methods=["GET", "POST"] if active_write_mode == "disabled" else ["GET", "POST", "PUT", "PATCH", "DELETE"],
        allow_headers=["*"],
    )

    @app.exception_handler(RequestValidationError)
    async def safe_upload_validation_error(request: Request, exc: RequestValidationError):
        if request.url.path.startswith("/api/postfinance/imports/"):
            fields = {str(error.get("loc", ("",))[-1]) for error in exc.errors()}
            if fields & {"zip_file_name", "zip_mime_type"}:
                detail = "Bitte ein unterstütztes PostFinance-ZIP auswählen."
            elif fields & {"overview_file_name", "overview_mime_type"}:
                detail = "Bitte eine offizielle Portfolioübersicht als PDF auswählen."
            else:
                detail = "Eine Datei ist leer, zu gross oder unvollständig übertragen worden."
            return JSONResponse(status_code=422, content={"detail": detail})
        return await request_validation_exception_handler(request, exc)

    @app.middleware("http")
    async def block_untrusted_writes(request: Request, call_next):
        if request.method == "POST" and request.url.path in POSTFINANCE_UPLOAD_PATHS:
            raw_length = request.headers.get("content-length", "")
            if not raw_length.isdigit():
                return JSONResponse(
                    status_code=411,
                    content={"detail": "Upload benötigt eine prüfbare Dateigrösse."},
                )
            if int(raw_length) > MAX_POSTFINANCE_REQUEST_BYTES:
                return JSONResponse(
                    status_code=413,
                    content={"detail": "Upload ist grösser als das sichere Verarbeitungslimit."},
                )
        read_only_post = request.method == "POST" and request.url.path in READ_ONLY_POST_PATHS
        if request.method == "POST" and request.url.path == "/api/market/equity/update-quotes/dry-run" and not is_local_request(request):
            return JSONResponse(status_code=403, content={"detail": "local_request_required"})
        if request.method in WRITE_METHODS and not read_only_post and not is_write_request_allowed(request, active_write_mode):
            return JSONResponse(status_code=403, content={"detail": "write_operations_disabled"})
        return await call_next(request)

    app.state.write_mode = active_write_mode
    for router in (health.router, overview.router, crypto.router, crypto_trader.router, equity.router, cash.router, reports.router, positions.router, budget.router, system.router, market.router, truewealth.router, postfinance.router):
        app.include_router(router, prefix="/api")
    return app


app = create_app()
