from fastapi import (
    FastAPI,
    HTTPException,
    BackgroundTasks,
    Request,
    Depends,
    APIRouter,
    Form,
    File,
    UploadFile,
)
from fastapi.concurrency import asynccontextmanager
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPBasicCredentials, HTTPBasic
from .config_loader import load_config
from fastapi.responses import HTMLResponse, PlainTextResponse, Response
from .models import WixFormSubmission
from datetime import datetime, date, timezone
from .auth import validate_api_key, validate_wix_signature, generate_api_key
from .rate_limit import (
    limiter,
    webhook_limiter,
    custom_rate_limit_handler,
    DEFAULT_RATE_LIMIT,
    WEBHOOK_RATE_LIMIT,
    BURST_RATE_LIMIT,
)
from slowapi.errors import RateLimitExceeded
import logging
from datetime import datetime
from typing import Dict, Any, Optional, List
import json
import os
import gzip
import xml.etree.ElementTree as ET
from .alpinebits_server import AlpineBitsServer, Version
import urllib.parse
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker

from .db import (
    Base,
    Customer as DBCustomer,
    Reservation as DBReservation,
    get_database_url,
)


# Configure logging
logging.basicConfig(level=logging.INFO)
_LOGGER = logging.getLogger(__name__)

# HTTP Basic auth for AlpineBits
security_basic = HTTPBasic()

# Load config at startup


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Setup DB

    try:
        config = load_config()
    except Exception as e:
        _LOGGER.error(f"Failed to load config: {str(e)}")
        config = {}
        
    DATABASE_URL = get_database_url(config)
    engine = create_async_engine(DATABASE_URL, echo=True)
    AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
    app.state.engine = engine
    app.state.async_sessionmaker = AsyncSessionLocal
    app.state.config = config
    app.state.alpine_bits_server = AlpineBitsServer(config)

    # Create tables
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    _LOGGER.info("Database tables checked/created at startup.")

    yield

    # Optional: Dispose engine on shutdown
    await engine.dispose()

async def get_async_session(request: Request):
    async_sessionmaker = request.app.state.async_sessionmaker
    async with async_sessionmaker() as session:
        yield session


app = FastAPI(
    title="Wix Form Handler API",
    description="Secure API endpoint to receive and process Wix form submissions with authentication and rate limiting",
    version="1.0.0",
    lifespan=lifespan
)

# Create API router with /api prefix
api_router = APIRouter(prefix="/api", tags=["api"])

# Add rate limiting
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, custom_rate_limit_handler)

# Add CORS middleware to allow requests from Wix
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://*.wix.com",
        "https://*.wixstatic.com",
        "http://localhost:3000",  # For development
        "http://localhost:8000",  # For local testing
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)


async def process_form_submission(submission_data: Dict[str, Any]) -> None:
    """
    Background task to process the form submission.
    Add your business logic here.
    """
    try:
        _LOGGER.info(
            f"Processing form submission: {submission_data.get('submissionId')}"
        )

        # Example processing - you can replace this with your actual logic
        form_name = submission_data.get("formName")
        contact_email = (
            submission_data.get("contact", {}).get("email")
            if submission_data.get("contact")
            else None
        )

        # Extract form fields
        form_fields = {
            k: v for k, v in submission_data.items() if k.startswith("field:")
        }

        _LOGGER.info(
            f"Form: {form_name}, Contact: {contact_email}, Fields: {len(form_fields)}"
        )

        # Here you could:
        # - Save to database
        # - Send emails
        # - Call external APIs
        # - Process the data further

    except Exception as e:
        _LOGGER.error(f"Error processing form submission: {str(e)}")




@api_router.get("/")
@limiter.limit(DEFAULT_RATE_LIMIT)
async def root(request: Request):
    """Health check endpoint"""
    return {
        "message": "Wix Form Handler API is running",
        "timestamp": datetime.now().isoformat(),
        "status": "healthy",
        "authentication": "required",
        "rate_limits": {
            "default": DEFAULT_RATE_LIMIT,
            "webhook": WEBHOOK_RATE_LIMIT,
            "burst": BURST_RATE_LIMIT,
        },
    }


@api_router.get("/health")
@limiter.limit(DEFAULT_RATE_LIMIT)
async def health_check(request: Request):
    """Detailed health check"""
    return {
        "status": "healthy",
        "timestamp": datetime.now().isoformat(),
        "service": "wix-form-handler",
        "version": "1.0.0",
        "authentication": "enabled",
        "rate_limiting": "enabled",
    }


# Extracted business logic for handling Wix form submissions
async def process_wix_form_submission(request: Request, data: Dict[str, Any], db):
    """
    Shared business logic for handling Wix form submissions (test and production).
    """
    timestamp = datetime.now().isoformat()

    _LOGGER.info(f"Received Wix form data at {timestamp}")
    # _LOGGER.info(f"Data keys: {list(data.keys())}")
    # _LOGGER.info(f"Full data: {json.dumps(data, indent=2)}")
    log_entry = {
        "timestamp": timestamp,
        "client_ip": request.client.host if request.client else "unknown",
        "headers": dict(request.headers),
        "data": data,
        "origin_header": request.headers.get("origin"),
        "all_headers": dict(request.headers),
    }
    logs_dir = "logs"
    if not os.path.exists(logs_dir):
        os.makedirs(logs_dir, mode=0o755, exist_ok=True)
        stat_info = os.stat(logs_dir)
        _LOGGER.info(
            f"Created directory owner: uid:{stat_info.st_uid}, gid:{stat_info.st_gid}"
        )
        _LOGGER.info(f"Directory mode: {oct(stat_info.st_mode)[-3:]}")
    log_filename = (
        f"{logs_dir}/wix_test_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
    )
    with open(log_filename, "w", encoding="utf-8") as f:
        json.dump(log_entry, f, indent=2, default=str, ensure_ascii=False)
    file_stat = os.stat(log_filename)
    _LOGGER.info(f"Created file owner: uid:{file_stat.st_uid}, gid:{file_stat.st_gid}")
    _LOGGER.info(f"File mode: {oct(file_stat.st_mode)[-3:]}")
    _LOGGER.info(f"Data logged to: {log_filename}")

    data = data.get("data")  # Handle nested "data" key if present

    # save customer and reservation to DB

    contact_info = data.get("contact", {})
    first_name = contact_info.get("name", {}).get("first")
    last_name = contact_info.get("name", {}).get("last")
    email = contact_info.get("email")
    phone_number = contact_info.get("phones", [{}])[0].get("e164Phone")
    locale = contact_info.get("locale", "de-de")
    contact_id = contact_info.get("contactId")

    name_prefix = data.get("field:anrede")
    email_newsletter = data.get("field:form_field_5a7b", "") != "Non selezionato"
    address_line = None
    city_name = None
    postal_code = None
    country_code = None
    gender = None
    birth_date = None
    language = data.get("contact", {}).get("locale", "en")[:2]

    # Dates
    start_date = (
        data.get("field:date_picker_a7c8")
        or data.get("Anreisedatum")
        or data.get("submissions", [{}])[1].get("value")
    )
    end_date = (
        data.get("field:date_picker_7e65")
        or data.get("Abreisedatum")
        or data.get("submissions", [{}])[2].get("value")
    )

    # Room/guest info
    num_adults = int(data.get("field:number_7cf5") or 2)
    num_children = int(data.get("field:anzahl_kinder") or 0)
    children_ages = []
    if num_children > 0:
        for k in data.keys():
            if k.startswith("field:alter_kind_"):
                try:
                    age = int(data[k])
                    children_ages.append(age)
                except ValueError:
                    _LOGGER.warning(f"Invalid age value for {k}: {data[k]}")

    offer = data.get("field:angebot_auswaehlen")

    # UTM and offer
    utm_fields = [
        ("utm_Source", "utm_source"),
        ("utm_Medium", "utm_medium"),
        ("utm_Campaign", "utm_campaign"),
        ("utm_Term", "utm_term"),
        ("utm_Content", "utm_content"),
    ]
    utm_comment_text = []
    for label, field in utm_fields:
        val = data.get(f"field:{field}") or data.get(label)
        if val:
            utm_comment_text.append(f"{label}: {val}")
    utm_comment = ",".join(utm_comment_text) if utm_comment_text else None

    # use database session

    # Save all relevant data to DB (including new fields)
    db_customer = DBCustomer(
        given_name=first_name,
        surname=last_name,
        contact_id=contact_id,
        name_prefix=name_prefix,
        email_address=email,
        phone=phone_number,
        email_newsletter=email_newsletter,
        address_line=address_line,
        city_name=city_name,
        postal_code=postal_code,
        country_code=country_code,
        gender=gender,
        birth_date=birth_date,
        language=language,
        address_catalog=False,
        name_title=None,
    )
    db.add(db_customer)
    await db.commit()
    await db.refresh(db_customer)

    db_reservation = DBReservation(
        customer_id=db_customer.id,
        form_id=data.get("submissionId"),
        start_date=date.fromisoformat(start_date) if start_date else None,
        end_date=date.fromisoformat(end_date) if end_date else None,
        num_adults=num_adults,
        num_children=num_children,
        children_ages=",".join(str(a) for a in children_ages),
        offer=offer,
        utm_comment=utm_comment,
        created_at=datetime.now(timezone.utc),
        utm_source=data.get("field:utm_source"),
        utm_medium=data.get("field:utm_medium"),
        utm_campaign=data.get("field:utm_campaign"),
        utm_term=data.get("field:utm_term"),
        utm_content=data.get("field:utm_content"),
        user_comment=data.get("field:long_answer_3524", ""),
        fbclid=data.get("field:fbclid"),
        gclid=data.get("field:gclid"),
        hotel_code="123",
        hotel_name="Frangart Inn",
    )
    db.add(db_reservation)
    await db.commit()
    await db.refresh(db_reservation)

    return {
        "status": "success",
        "message": "Wix form data received successfully",
        "received_keys": list(data.keys()),
        "data_logged_to": log_filename,
        "timestamp": timestamp,
        "note": "No authentication required for this endpoint",
    }


@api_router.post("/webhook/wix-form")
@webhook_limiter.limit(WEBHOOK_RATE_LIMIT)
async def handle_wix_form(
    request: Request, data: Dict[str, Any], db_session=Depends(get_async_session)
):
    """
    Unified endpoint to handle Wix form submissions (test and production).
    No authentication required for this endpoint.
    """
    try:
        return await process_wix_form_submission(request, data, db_session)
    except Exception as e:
        _LOGGER.error(f"Error in handle_wix_form: {str(e)}")
        # log stacktrace
        import traceback

        traceback_str = traceback.format_exc()
        _LOGGER.error(f"Stack trace for handle_wix_form: {traceback_str}")
        raise HTTPException(
            status_code=500, detail=f"Error processing Wix form data: {str(e)}"
        )


@api_router.post("/webhook/wix-form/test")
@limiter.limit(DEFAULT_RATE_LIMIT)
async def handle_wix_form_test(
    request: Request, data: Dict[str, Any], db_session=Depends(get_async_session)
):
    """
    Test endpoint to verify the API is working with raw JSON data.
    No authentication required for testing purposes.
    """
    try:
        return await process_wix_form_submission(request, data, db_session)
    except Exception as e:
        _LOGGER.error(f"Error in handle_wix_form_test: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"Error processing test data: {str(e)}"
        )


@api_router.post("/admin/generate-api-key")
@limiter.limit("5/hour")  # Very restrictive for admin operations
async def generate_new_api_key(
    request: Request, admin_key: str = Depends(validate_api_key)
):
    """
    Admin endpoint to generate new API keys.
    Requires admin API key and is heavily rate limited.
    """
    if admin_key != "admin-key":
        raise HTTPException(status_code=403, detail="Admin access required")

    new_key = generate_api_key()
    _LOGGER.info(f"Generated new API key (requested by: {admin_key})")

    return {
        "status": "success",
        "message": "New API key generated",
        "api_key": new_key,
        "timestamp": datetime.now().isoformat(),
        "note": "Store this key securely - it won't be shown again",
    }


async def validate_basic_auth(
    credentials: HTTPBasicCredentials = Depends(security_basic),
) -> str:
    """
    Validate basic authentication for AlpineBits protocol.
    Returns username if valid, raises HTTPException if not.
    """
    # Accept any username/password pair present in config['alpine_bits_auth']
    if not credentials.username or not credentials.password:
        raise HTTPException(
            status_code=401,
            detail="ERROR: Authentication required",
            headers={"WWW-Authenticate": "Basic"},
        )
    valid = False
    config = app.state.config

    for entry in config["alpine_bits_auth"]:
        if (
            credentials.username == entry["username"]
            and credentials.password == entry["password"]
        ):
            valid = True
            break
    if not valid:
        raise HTTPException(
            status_code=401,
            detail="ERROR: Invalid credentials",
            headers={"WWW-Authenticate": "Basic"},
        )
    _LOGGER.info(
        f"AlpineBits authentication successful for user: {credentials.username} (from config)"
    )
    return credentials.username, credentials.password


def parse_multipart_data(content_type: str, body: bytes) -> Dict[str, Any]:
    """
    Parse multipart/form-data from raw request body.
    This is a simplified parser for the AlpineBits use case.
    """
    if "multipart/form-data" not in content_type:
        raise HTTPException(
            status_code=400, detail="ERROR: Content-Type must be multipart/form-data"
        )

    # Extract boundary
    boundary = None
    for part in content_type.split(";"):
        part = part.strip()
        if part.startswith("boundary="):
            boundary = part.split("=", 1)[1].strip('"')
            break

    if not boundary:
        raise HTTPException(
            status_code=400, detail="ERROR: Missing boundary in multipart/form-data"
        )

    # Simple multipart parsing
    parts = body.split(f"--{boundary}".encode())
    data = {}

    for part in parts:
        if not part.strip() or part.strip() == b"--":
            continue

        # Split headers and content
        if b"\r\n\r\n" in part:
            headers_section, content = part.split(b"\r\n\r\n", 1)
            content = content.rstrip(b"\r\n")

            # Parse Content-Disposition header
            headers = headers_section.decode("utf-8", errors="ignore")
            name = None
            for line in headers.split("\n"):
                if "Content-Disposition" in line and "name=" in line:
                    # Extract name parameter
                    for param in line.split(";"):
                        param = param.strip()
                        if param.startswith("name="):
                            name = param.split("=", 1)[1].strip('"')
                            break

            if name:
                # Handle file uploads or text content
                if content.startswith(b"<"):
                    # Likely XML content
                    data[name] = content.decode("utf-8", errors="ignore")
                else:
                    data[name] = content.decode("utf-8", errors="ignore")

    return data


@api_router.post("/alpinebits/server-2024-10")
@limiter.limit("60/minute")
async def alpinebits_server_handshake(
    request: Request, credentials_tupel: tuple = Depends(validate_basic_auth), dbsession=Depends(get_async_session)
):
    """
    AlpineBits server endpoint implementing the handshake protocol.

    This endpoint handles:
    - Protocol version negotiation via X-AlpineBits-ClientProtocolVersion header
    - Client identification via X-AlpineBits-ClientID header (optional)
    - Multipart/form-data parsing for action and request parameters
    - Gzip compression support
    - Proper error handling with HTTP status codes
    - Handshaking action processing

    Authentication: HTTP Basic Auth required
    Content-Type: multipart/form-data
    Compression: gzip supported (check X-AlpineBits-Server-Accept-Encoding)
    """
    try:
        # Check required headers
        client_protocol_version = request.headers.get(
            "X-AlpineBits-ClientProtocolVersion"
        )
        if not client_protocol_version:
            # Server concludes client speaks a protocol version preceding 2013-04
            client_protocol_version = "pre-2013-04"
            _LOGGER.info(
                "No X-AlpineBits-ClientProtocolVersion header found, assuming pre-2013-04"
            )
        else:
            _LOGGER.info(f"Client protocol version: {client_protocol_version}")

        # Optional client ID
        client_id = request.headers.get("X-AlpineBits-ClientID")
        if client_id:
            _LOGGER.info(f"Client ID: {client_id}")

        # Check content encoding
        content_encoding = request.headers.get("Content-Encoding")
        is_compressed = content_encoding == "gzip"

        if is_compressed:
            _LOGGER.info("Request is gzip compressed")

        # Get content type before processing
        content_type = request.headers.get("Content-Type", "")

        _LOGGER.info(f"Content-Type: {content_type}")
        _LOGGER.info(f"Content-Encoding: {content_encoding}")

        # Get request body
        body = await request.body()

        # Decompress if needed
        if is_compressed:
            try:
                body = gzip.decompress(body)

            except Exception as e:
                raise HTTPException(
                    status_code=400,
                    detail=f"ERROR: Failed to decompress gzip content: {str(e)}",
                )

        # Check content type (after decompression)
        if (
            "multipart/form-data" not in content_type
            and "application/x-www-form-urlencoded" not in content_type
        ):
            raise HTTPException(
                status_code=400,
                detail="ERROR: Content-Type must be multipart/form-data or application/x-www-form-urlencoded",
            )

        # Parse multipart data
        if "multipart/form-data" in content_type:
            try:
                form_data = parse_multipart_data(content_type, body)
            except Exception as e:
                raise HTTPException(
                    status_code=400,
                    detail=f"ERROR: Failed to parse multipart/form-data: {str(e)}",
                )
        elif "application/x-www-form-urlencoded" in content_type:
            # Parse as urlencoded
            form_data = dict(urllib.parse.parse_qsl(body.decode("utf-8")))
        else:
            raise HTTPException(
                status_code=400,
                detail="ERROR: Content-Type must be multipart/form-data or application/x-www-form-urlencoded",
            )

        # Check for required action parameter
        action = form_data.get("action")
        if not action:
            raise HTTPException(
                status_code=400, detail="ERROR: Missing required 'action' parameter"
            )

        _LOGGER.info(f"AlpineBits action: {action}")

        # Get optional request XML
        request_xml = form_data.get("request")

        server = app.state.alpine_bits_server

        version = Version.V2024_10

        username, password = credentials_tupel

        # Create successful handshake response
        response = await server.handle_request(action, request_xml, version, dbsession=dbsession, username=username, password=password)

        response_xml = response.xml_content

        # Set response headers indicating server capabilities
        headers = {
            "Content-Type": "application/xml; charset=utf-8",
            "X-AlpineBits-Server-Accept-Encoding": "gzip",  # Indicate gzip support
            "X-AlpineBits-Server-Version": "2024-10",
        }

        return Response(
            content=response_xml, status_code=response.status_code, headers=headers
        )

    except HTTPException:
        # Re-raise HTTP exceptions (auth errors, etc.)
        raise
    except Exception as e:
        _LOGGER.error(f"Error in AlpineBits handshake: {str(e)}")
        raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")


@api_router.get("/admin/stats")
@limiter.limit("10/minute")
async def get_api_stats(request: Request, admin_key: str = Depends(validate_api_key)):
    """
    Admin endpoint to get API usage statistics.
    Requires admin API key.
    """
    if admin_key != "admin-key":
        raise HTTPException(status_code=403, detail="Admin access required")

    # In a real application, you'd fetch this from your database/monitoring system
    return {
        "status": "success",
        "stats": {
            "uptime": "Available in production deployment",
            "total_requests": "Available with monitoring setup",
            "active_api_keys": len([k for k in ["wix-webhook-key", "admin-key"] if k]),
            "rate_limit_backend": "redis" if os.getenv("REDIS_URL") else "memory",
        },
        "timestamp": datetime.now().isoformat(),
    }


# Include the API router in the main app
app.include_router(api_router)


@app.get("/", response_class=HTMLResponse)
async def landing_page():
    """
    Serve the under construction landing page at the root route
    """
    try:
        # Get the path to the HTML file
        import os

        html_path = os.path.join(os.path.dirname(__file__), "templates", "index.html")

        with open(html_path, "r", encoding="utf-8") as f:
            html_content = f.read()

        return HTMLResponse(content=html_content, status_code=200)
    except FileNotFoundError:
        # Fallback if HTML file is not found
        html_content = """
        <!DOCTYPE html>
        <html>
        <head>
            <title>99tales - Under Construction</title>
            <style>
                body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
                h1 { color: #333; }
            </style>
        </head>
        <body>
            <h1>🏗️ 99tales</h1>
            <h2>Under Construction</h2>
            <p>We're working hard to bring you something amazing!</p>
            <p><a href="/api">API Documentation</a></p>
        </body>
        </html>
        """
        return HTMLResponse(content=html_content, status_code=200)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)
