#pip install streamlit fastapi uvicorn
import streamlit as st
import sys
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException, status, Body, Request, Response
from fastapi.security import OAuth2PasswordRequestForm,OAuth2PasswordBearer
from fastapi.responses import JSONResponse

from typing import Optional, Tuple, Callable, Dict

from datetime import timedelta
from utils.library.db import Db
import json
from typing import Dict,List,Any
from utils.library.syslog import savelog
from utils.library.param import get_Param
from utils.library.resources import get_api_started
from utils.pyx.api_run import process_data,api_get_doc
from utils.pyx._api_auth import _get_current_user,_get_current_user_by_session,authenticate_user, create_access_token, get_bearer_token_optional,limiter,WINDOW_SECONDS,MAX_REQUESTS,make_key


ACCESS_TOKEN_EXPIRE_MINUTES=60


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str  = Depends(oauth2_scheme)):
    return await _get_current_user(token)

async def get_current_user_by_session(request = Request, token: str  = Depends(get_bearer_token_optional)):
    return await _get_current_user_by_session(request,token)


@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    

app_FastAPI = FastAPI(lifespan=lifespan)

MAX_BODY = 10 * 1024 * 1024  # 10MB

@app_FastAPI.middleware("http")
async def limit_body(request: Request, call_next):
    cl = request.headers.get("content-length")
    if cl and int(cl) > MAX_BODY:
        raise HTTPException(status_code=413, detail="Payload Too Large")
    return await call_next(request)

@app_FastAPI.exception_handler(Exception)
async def handle_rl(request: Request, exc: Exception):
    # fastapi-limiter lança StarletteHTTPException 429; tratamos generico pra simplificar
    if getattr(exc, "status_code", None) == 429:
        return JSONResponse({"detail": "Too Many Requests"}, status_code=429)
    raise exc
# Extrai user_id do seu validador _get_current_user, se houver token.
# NÃO obriga login — apenas preenche request.state.user_id quando possível.
async def current_user_id_optional(
    request: Request,
    token: Optional[str] = Depends(get_bearer_token_optional),
) -> Optional[str]:
    if not token:
        return None
    try:
        # Reuse sua função/fluxo existente:
        user = await _get_current_user(token)  # <- sua função
        # Pegue um identificador estável do usuário:
        uid = str(
            user.get("id")
            or user.get("sub")
            or user.get("username")
            or user.get("email")
        )
        request.state.user_id = uid
        return uid
    except Exception:
        # Token inválido → trata como não autenticado
        return None

# ===== Dependência de rate limit =====
async def rate_limited(
    request: Request,
    uid: Optional[str] = Depends(current_user_id_optional),
):
    key = make_key(request, uid)
    allowed, remaining, retry_after = await limiter.check(key)
    if not allowed:
        headers = {
            "Retry-After": str(retry_after),
            "X-RateLimit-Limit": str(MAX_REQUESTS),
            "X-RateLimit-Remaining": str(remaining),
        }
        raise HTTPException(status_code=429, detail="Too Many Requests", headers=headers)

@app_FastAPI.get("/robots.txt")
def robots():
    body = "User-agent: *\nDisallow: /admin\n"
    return Response(content=body, media_type="text/plain")

@app_FastAPI.get("/healthz")
def health():
    return {"status":"ok"}

@app_FastAPI.post("/token", response_model=dict, dependencies=[Depends(rate_limited)])
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form_data.username, form_data.password)
    #user = authenticate_user(form_data.client_id, form_data.client_secret)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user['email']}, expires_delta=access_token_expires
    )
    savelog('api',pClass=__file__,pDef='login_for_access_token',pMessage=f'Efetuou login na API {user["email"]} {str(access_token)}')
    return {"access_token": access_token, "token_type": "bearer"}

@app_FastAPI.post("/process", dependencies=[Depends(rate_limited)])
async def process_process(data: List[Dict] , current_user: dict = Depends(get_current_user)):
    return process_data(data,'PROCESS',current_user)
 

@app_FastAPI.get("/apiauth/{method}", dependencies=[Depends(rate_limited)])
async def process_get(method:str , request:Request, current_user: dict = Depends(get_current_user)):
    parametros: Dict[str, str] = dict(request.query_params)
    return process_data('{"'+method+'":'+json.dumps(parametros)+'}','API',current_user)

@app_FastAPI.get("/apiauth/{method}/doc", dependencies=[Depends(rate_limited)])
async def process_get(method:str  , current_user: dict = Depends(get_current_user)):
    return api_get_doc(method,current_user)
 

@app_FastAPI.post("/apiauth/{method}", dependencies=[Depends(rate_limited)])
async def process_get(method:str ,  data: Dict[str, Any] = Body(...), current_user: dict = Depends(get_current_user)):
    return process_data('{"'+method+'":'+json.dumps(data)+'}','API',current_user)

@app_FastAPI.post("/apiauth/{method}/doc", dependencies=[Depends(rate_limited)])
async def process_get(method:str  , current_user: dict = Depends(get_current_user)):
    return api_get_doc(method,current_user)
 
@app_FastAPI.get("/api/{method}", dependencies=[Depends(rate_limited)])
async def process_get(method:str , request:Request):
    parametros: Dict[str, str] = dict(request.query_params)
    return process_data('{"'+method+'":'+json.dumps(parametros)+'}','API',None)

@app_FastAPI.get("/api/{method}/doc", dependencies=[Depends(rate_limited)])
async def process_get(method:str):
    return api_get_doc(method,None)
 
@app_FastAPI.post("/api/{method}", dependencies=[Depends(rate_limited)])
async def process_get(method:str ,  data: Dict[str, Any] = Body(...)):
    return process_data('{"'+method+'":'+json.dumps(data)+'}','API',None)

@app_FastAPI.post("/api/{method}/doc", dependencies=[Depends(rate_limited)])
async def process_get(method:str):
    return api_get_doc(method,None)

@app_FastAPI.post("/", dependencies=[Depends(rate_limited)])
async def process_post(data=Dict, current_user: dict = Depends(get_current_user)):
    return process_data(data,'POST',current_user)


@app_FastAPI.put("/", dependencies=[Depends(rate_limited)])
async def process_put(data=Dict, current_user: dict = Depends(get_current_user)):
    return process_data(data,'PUT',current_user)

@app_FastAPI.delete("/", dependencies=[Depends(rate_limited)])
async def process_delete(data=Dict,current_user: dict = Depends(get_current_user)):
     return process_data(data,'DELETE',current_user)

@app_FastAPI.post("/db")
async def process_delete(data=Dict,request=Request,current_user: dict = Depends(get_current_user_by_session)):
     return process_data(data,'db',current_user,request)



def start_api_thread():
    start_api_host = get_Param('sys_api_host','localhost')
    start_api_port = get_Param('sys_api_port','8502')
    ssl_certfile=st.get_option("server.sslCertFile")
    if not ssl_certfile and 'sslCertFile' in sys.argv:
        ssl_certfile = sys.argv['sslCertFile']
    
    ssl_keyfile=st.get_option("server.sslKeyFile")
    if not ssl_keyfile and 'ssl_keyfile' in sys.argv:
        ssl_keyfile = sys.argv['ssl_keyfile']

    import uvicorn
    uvicorn.run("utils.library.api:app_FastAPI", host=start_api_host, port=int(start_api_port),
                ssl_certfile=ssl_certfile,
                ssl_keyfile=ssl_keyfile)

def start_api():
    import threading
    api_thread = threading.Thread(target=start_api_thread, daemon=True)
    api_thread.start()
    return 1

    
conta_api =   get_api_started()

if conta_api['api_started'] == 0 or 'runapianyway' in sys.argv:
    start_apixxxx = get_Param('sys_api_start','false').lower()
    if start_apixxxx == 'true'  or 'runapianyway' in sys.argv:
        if not os.path.exists(".streamlit/.noapi") or 'runapianyway' in sys.argv:
            conta_api['api_started']  = start_api()
