from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse

import requests as http_requests
import os
import jwt
import uuid
import logging
from logging.handlers import RotatingFileHandler
from datetime import datetime, timedelta
from dotenv import load_dotenv

from .api import endpoints
from .db.database import Database

# Load configurations
load_dotenv()

# Constants from .env
FRONTEND_URL = os.getenv("FRONTEND_URL_PROD", "http://localhost:3000")
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
# This must match the value registered in Google Cloud Console.
GOOGLE_REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI", "https://test.jarednevans.com/chat")
SECRET_KEY = os.getenv("MIDDLEWARE_SECRET_KEY")

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        RotatingFileHandler(
            "/var/log/doclink/doclink.log",
            maxBytes=10000000,  # 10MB
            backupCount=5,
        ),
        logging.StreamHandler(),
    ],
)
logger = logging.getLogger(__name__)

# App initialization
app = FastAPI(title="Doclink")
app.mount("/static", StaticFiles(directory="app/static"), name="static")
templates = Jinja2Templates(directory="templates")


# Middleware for security headers
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    response.headers["Content-Security-Policy"] = (
        "default-src 'self';"
        "script-src 'self' 'unsafe-inline' 'unsafe-eval' "
        "https://cdnjs.cloudflare.com "
        "https://www.googletagmanager.com "
        "https://www.google-analytics.com "
        "https://cdn.jsdelivr.net;"
        "style-src 'self' 'unsafe-inline' "
        "https://fonts.googleapis.com "
        "https://cdn.jsdelivr.net "
        "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/ "
        "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/;"
        "style-src-elem 'self' 'unsafe-inline' "
        "https://fonts.googleapis.com "
        "https://cdn.jsdelivr.net "
        "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/ "
        "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/;"
        "font-src 'self' https://fonts.gstatic.com "
        "https://cdn.jsdelivr.net data:;"
        "img-src 'self' data: https://www.google-analytics.com https://*.googleusercontent.com;"
        "connect-src 'self' https://www.google-analytics.com;"
    )
    return response


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


def exchange_code_for_token(code: str) -> dict:
    """
    Exchange the authorization code for an access token.
    """
    token_endpoint = "https://oauth2.googleapis.com/token"
    data = {
        "code": code,
        "client_id": GOOGLE_CLIENT_ID,
        "client_secret": GOOGLE_CLIENT_SECRET,
        "redirect_uri": GOOGLE_REDIRECT_URI,
        "grant_type": "authorization_code",
    }
    response = http_requests.post(token_endpoint, data=data)
    if response.status_code != 200:
        raise Exception(f"Failed to exchange code: {response.text}")
    return response.json()


async def verify_google_token(token: str) -> dict:
    """
    Use the access token to fetch user info from Google.
    """
    try:
        userinfo_response = http_requests.get(
            "https://www.googleapis.com/oauth2/v3/userinfo",
            headers={"Authorization": f"Bearer {token}"},
        )
        if not userinfo_response.ok:
            raise ValueError("Failed to get user info")
        userinfo = userinfo_response.json()
        if not userinfo.get("sub"):
            raise ValueError("Invalid user info")
        return userinfo
    except Exception as e:
        logger.info(f"Token verification error: {str(e)}")
        raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")


def create_session_token(user_data: dict) -> str:
    """
    Create an encrypted session token.
    """
    payload = {
        "user_id": user_data["user_id"],
        "email": user_data["email"],
        "exp": datetime.utcnow() + timedelta(days=1),  # 1 day expiration
    }
    return jwt.encode(payload, SECRET_KEY, algorithm="HS256")


def verify_session_token(session_token: str) -> dict:
    """
    Verify and decode the session token.
    """
    try:
        payload = jwt.decode(session_token, SECRET_KEY, algorithms=["HS256"])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Session expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid session")


@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    """
    Middleware to check authentication for protected routes.
    """
    public_paths = {"/api/version", "/docs", "/redoc", "/openapi.json"}
    if request.url.path in public_paths:
        return await call_next(request)
    if request.url.path.startswith("/chat"):
        session_cookie = request.cookies.get("session_token")
        if session_cookie:
            try:
                user_data = verify_session_token(session_cookie)
                request.state.user_data = user_data
                return await call_next(request)
            except Exception as e:
                logger.info(f"Error validating session cookie: {e}")
                return RedirectResponse(url=FRONTEND_URL)
    return await call_next(request)


@app.get("/")
async def index(request: Request):
    session_id = str(uuid.uuid4())
    # Build the Google OAuth URL using the authorization code flow
    google_oauth_url = (
        "https://accounts.google.com/o/oauth2/v2/auth"
        f"?client_id={GOOGLE_CLIENT_ID}"
        f"&redirect_uri={GOOGLE_REDIRECT_URI}"
        "&response_type=code"
        "&scope=openid%20email%20profile"
        f"&state={session_id}"
    )
    return templates.TemplateResponse("index.html", {"request": request, "google_oauth_url": google_oauth_url})


@app.get("/chat")
async def chat_page(request: Request):
    code = request.query_params.get("code")
    state = request.query_params.get("state")
    if not code or not state:
        raise HTTPException(status_code=400, detail="Missing code or state")
    session_id = state  # Use state as the session identifier
    try:
        # Exchange the authorization code for an access token
        token_response = exchange_code_for_token(code)
        access_token = token_response.get("access_token")
        if not access_token:
            raise Exception("No access token received")

        # Retrieve user info from Google
        google_user = await verify_google_token(access_token)

        # Check if the user's name starts with 'Jared' or 'Elizabeth'
        user_name = google_user.get("name", "")
        if not (user_name.startswith("Jared") or user_name.startswith("Elizabeth")):
            logger.info(f"Access denied for user: {user_name} (ID: {google_user.get('sub')})")
            raise HTTPException(
                status_code=403,
                detail="Access restricted to pre-approved users.'"
            )

        # Log user info
        logger.info(f"User authenticated: {user_name} (ID: {google_user.get('sub')})")

        # Build the user data
        user_data = {
            "user_id": google_user.get("sub"),
            "email": google_user.get("email"),
            "name": user_name,
            "picture": google_user.get("picture"),
        }

        session_token = create_session_token(user_data)
        is_first_time = False

        # Check local database for an existing record
        with Database() as db:
            existing_user, _ = db.get_user_info_w_id(user_data["user_id"])
            if not existing_user:
                is_first_time = True
                # Insert new user record into user_info
                db.insert_user_info(
                    user_id=user_data["user_id"],
                    google_id=user_data["user_id"],  # using the sub as google_id
                    user_name=user_data["name"],
                    user_surname="",
                    user_email=user_data["email"],
                    user_type="free",
                    picture_url=user_data["picture"],
                )
                # Insert default domain and user guide
                domain_id = str(uuid.uuid4())
                db.insert_domain_info(
                    user_id=user_data["user_id"],
                    domain_id=domain_id,
                    domain_name="My First Folder",
                    domain_type=0,
                )
                db.insert_user_guide(
                    user_id=user_data["user_id"],
                    domain_id=domain_id
                )

        response = templates.TemplateResponse(
            "app.html",
            {
                "request": request,
                "session_id": session_id,
                "user_id": user_data["user_id"],
                "is_first_time": is_first_time,
                "environment": "prod",
            },
        )
        response.set_cookie(
            key="session_token",
            value=session_token,
            httponly=True,
            secure=False,
            max_age=86400,  # 1 day
            samesite="lax",
        )
        return response
    except Exception as e:
        logger.info(f"Error processing chat page: {e}")
        raise HTTPException(status_code=500, detail=f"Error rendering application: {e}")


app.include_router(endpoints.router, prefix="/api/v1")

