import os
import asyncio
import base64
import tempfile
import subprocess
from fastapi import FastAPI, WebSocket, WebSocketDisconnect,Depends,APIRouter,HTTPException,UploadFile,Form,Header,BackgroundTasks
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from app.core.keycloak import verify_jwt,get_client_token,validate_token
from app.core.logger import log_async
from app.core.database.models.InstallToken import InstallToken
from sqlalchemy.orm import Session
from app.core.database.connections import get_db
import uuid
from datetime import datetime
from fastapi.responses import FileResponse
from pathlib import Path
from fastapi.responses import JSONResponse
from fastapi import Request
from app.core.database.models.Campaings import NameList
import json
from app.apis.apiV1.Controllers.ApiController import crear_elemento ,update_element,crear_elemento_def
from typing import Literal
from app.core import config
from jose import jwt
from fastapi import Query
import asyncio

SECRET_KEY = "clave-secreta-hls"
BASE_PATH = "/var/www/api-certificacion/app/streams"


app = FastAPI()
router = APIRouter()
# Permitir orígenes (puedes poner la url de tu frontenfrom datetime import datetime

origins = [
    "http://localhost",  # Cambia esto por la URL de tu front
    "*",  # Para permitir todos los orígenes (no recomendado en producción)
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # Lista de orígenes permitidos
    allow_credentials=True,
    allow_methods=["*"],    # Permitir todos los métodos (GET, POST, etc)
    allow_headers=["*"],    # Permitir todos los headers
)

CURRENT_DIR = Path(__file__).parent.resolve()
APP_DIR = CURRENT_DIR.parent
CERTS_DIR = APP_DIR / "certs"
CA_KEY = CERTS_DIR / "ca.key"
CA_CRT = CERTS_DIR / "ca.crt"
SIGNED_DIR = CERTS_DIR /"signed"
UPLOAD_DIR = CERTS_DIR /"csrs"
os.makedirs(SIGNED_DIR, exist_ok=True)
os.makedirs(UPLOAD_DIR, exist_ok=True)
API_URL=config.API_URL
INDEX_STREMING='index.m3u8'
# Base donde se almacenan las carpetas de cada cámara con HLS
BASE_STREAM_PATH = APP_DIR /"streams"

# Montar la carpeta base para servir archivos estáticos HLS

@router.get("/streams/{cam_id}/{filename}")
async def get_stream_file(cam_id: str, filename: str, Authorization: str = Header(...)):
    token = Authorization.split(" ")[1]
    decoded_token = validate_token(token)
    path = BASE_STREAM_PATH / cam_id / filename
    if not path.exists():
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail="Archivo no encontrado")
    return FileResponse(path)
@router.get("/verify-token/{token}")
def verify_token(request: Request,token: str, db: Session = Depends(get_db)):
    token_obj = db.query(InstallToken).filter_by(token=token).first()
    if not token_obj:
        raise HTTPException(status_code=404, detail="Token no encontrado")
    if token_obj.used:
        raise HTTPException(status_code=400, detail="Token ya fue usado")
    client_ip = request.client.host
    token_obj.used = True
    token_obj.ip = client_ip
    token_obj.used_at = datetime.utcnow()
    #db.commit()
    return {"valid": True, "user": token_obj.user, "created_at": token_obj.created_at}
@router.get("/keycloack-test")
def keycloack_token():
    return 1
@router.post("/register")
async def register(
    request: Request,
   # token: str = Form(...),
    striming: str = Form(...),
    address: str = Form(...),
    country: Literal["Chile", "Mexico", "Peru"] = Form(...),
    code: str = Form(...),
    csr_file: UploadFile = Form(...),
    db: Session = Depends(get_db)
):
    # 1. Validar token en DB
    """
    token_obj = db.query(InstallToken).filter_by(token=token).first()
    if not token_obj:
        raise HTTPException(status_code=404, detail="Token no encontrado")
    if token_obj.used:
        raise HTTPException(status_code=400, detail="Token ya fue usado")
    client_ip = request.client.host
    """
    # 2. Guardar CSR temporalmente
    uid = str(uuid.uuid4())
    csr_path = UPLOAD_DIR / f"{uid}.csr"
    crt_path = SIGNED_DIR / f"{uid}.crt"

    csr_content = await csr_file.read()
    with open(csr_path, "wb") as f:
        f.write(csr_content)

    # 3. Firmar CSR usando OpenSSL
    try:
        subprocess.run([
            "openssl", "x509", "-req",
            "-in", str(csr_path),
            "-CA", str(CA_CRT),
            "-CAkey", str(CA_KEY),
            "-CAcreateserial",
            "-out", str(crt_path),
            "-days", "365",
            "-sha256"
        ], check=True)
    except subprocess.CalledProcessError as e:
        raise HTTPException(status_code=500, detail=f"Error firmando CSR: {e}")
    """
    # 4. Marcar token como usado
    token_obj.used = True
    token_obj.ip = client_ip
    token_obj.element_code = code
    token_obj.used_at = datetime.utcnow()
    """
    #db.commit()
    token = await get_client_token()
    url_striming=f"{API_URL}/streams/{code}/{INDEX_STREMING}"
    response = await crear_elemento(token,code,address,url_striming,country)
    Path((BASE_STREAM_PATH) / code).mkdir(parents=True, exist_ok=True)
    # 5. Leer certificados firmados y CA como texto PEM
    with open(crt_path, "r") as f:
        signed_cert_pem = f.read()
    with open(CA_CRT, "r") as f:
        ca_cert_pem = f.read()

    # 6. Devolver ambos certificados en JSON
    return JSONResponse(content={
        "client_cert": signed_cert_pem,
        "ca_cert": ca_cert_pem,
    })
@router.get("/valid-token")
async def get_stream_file(user=Depends(verify_jwt)):
#async def get_stream_file():

    return user
#app.mount("/streams", StaticFiles(directory=BASE_STREAM_PATH), name="streams")
"""
async def run_ffmpeg(pipe_path: str, output_dir: str):
    os.makedirs(output_dir, exist_ok=True)
    playlist_path = os.path.join(output_dir, "playlist.m3u8")
    segment_path = os.path.join(output_dir, "segment%d.ts")

    # Comando FFmpeg para convertir MJPEG fifo a HLS
    cmd = [
    "ffmpeg",
    "-y",  # overwrite output
    "-f", "mjpeg",
    "-r", "5",  # <-- aquí ajustas a la velocidad real de cuadros por segundo
    "-i", pipe_path,
    "-c:v", "libx264",
    "-preset", "veryfast",
    "-f", "hls",
    "-hls_time", "4",
    "-hls_list_size", "5",
    "-hls_flags", "delete_segments",
    "-hls_segment_filename", segment_path,
    playlist_path,
]
    process = await asyncio.create_subprocess_exec(*cmd,
                                                   stdout=asyncio.subprocess.PIPE,
                                                   stderr=asyncio.subprocess.PIPE)
    await process.wait()
"""
@router.websocket("/ws-old/{cam_id}")
async def websocket_endpoint_old(websocket: WebSocket, cam_id: str):
    await websocket.accept()

    stream_dir = f"/var/www/api-certificacion/app/streams/{cam_id}"
    os.makedirs(stream_dir, exist_ok=True)
    """
    ffmpeg_cmd = [
    "ffmpeg",
    "-y",
    "-f", "mjpeg",
    "-framerate", "10",
    "-use_wallclock_as_timestamps", "1",
    "-i", "-",
    "-c:v", "libx264",
    "-preset", "ultrafast",
    "-tune", "zerolatency",
    "-r", "10",
    "-vsync", "cfr",                # fuerza framerate constante
    "-bf", "0",
    "-fflags", "+genpts",
    "-flags", "+cgop",
    "-g", "20",
    "-keyint_min", "20",
    "-sc_threshold", "0",
    "-start_number", "0",           # numeración continua
    "-f", "hls",
    "-hls_time", "2",
    "-hls_list_size", "120",          # más buffer
    "-hls_flags", "delete_segments+split_by_time+append_list",
    f"{stream_dir}/index.m3u8",
    "-hide_banner",
    "-loglevel", "error"
]
   """ 
    ffmpeg_cmd = [
        "ffmpeg",
        "-y",
        "-f", "mjpeg",
        "-framerate", "5",
        "-i", "-",
        "-c:v", "libx264",
        "-preset", "ultrafast",
        "-tune", "zerolatency",
         "-r", "5",  # salida
        "-fflags", "+genpts",
        "-flags", "+cgop",
        "-g", "25",
        "-keyint_min", "25",
        "-sc_threshold", "0",
        "-f", "hls",
        "-hls_time", "1",
        "-hls_list_size", "120",
        "-hls_flags", "delete_segments",
        "-loglevel", "error",
        f"{stream_dir}/index.m3u8"
    ]
   
    #process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE)
    process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE, bufsize=0)

    try:
        while True:
            data = await websocket.receive_text()
            frame_data = base64.b64decode(data)
            process.stdin.write(frame_data)
            process.stdin.flush()
    except WebSocketDisconnect:
        print(f"📴 WebSocket cerrado: {cam_id}")
    finally:
        process.stdin.close()
        process.wait()
        
@router.websocket("/ws/{cam_id}")
async def websocket_endpoint(websocket: WebSocket, cam_id: str):
    await websocket.accept()

    stream_dir = f"/var/www/api-certificacion/app/streams/{cam_id}"
    os.makedirs(stream_dir, exist_ok=True)

    def start_ffmpeg():
        
        ffmpeg_cmd = [
    "ffmpeg",
    "-y", "-hide_banner", "-loglevel", "error",
    # ENTRADA: MJPEG con timestamps reales
    "-f", "mjpeg",
    "-use_wallclock_as_timestamps", "1",
    "-i", "-",
    # ENCODE
    "-an",
    "-c:v", "libx264",
    "-preset", "ultrafast",
    "-tune", "zerolatency",
    "-pix_fmt", "yuv420p",
    "-bf", "0",
    "-sc_threshold", "0",
    "-r", "5",  # salida estabilizada a 5 fps
    # GOP = 4s (5 fps → 20 frames). Cada segmento cae en IDR.
    "-g", "20",
    "-keyint_min", "20",
    "-force_key_frames", "expr:gte(t,n_forced*4)",
    # HLS
    "-f", "hls",
    "-hls_time", "4",
    "-hls_list_size", "60",
    "-hls_flags", "independent_segments+temp_file+delete_segments",
    "-hls_allow_cache", "0",
    f"{stream_dir}/index.m3u8",
]


        return subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE, bufsize=0)

    process = start_ffmpeg()

    try:
        while True:
            try:
                data = await websocket.receive_text()
                frame_data = base64.b64decode(data)

                # Solo escribir si FFmpeg sigue vivo
                if process.poll() is None:
                    process.stdin.write(frame_data)
                    process.stdin.flush()
                else:
                    print(f"❌ FFmpeg para {cam_id} se cerró, reiniciando...")
                    process = start_ffmpeg()

            except BrokenPipeError:
                print(f"❌ Broken pipe en FFmpeg para {cam_id}, reiniciando...")
                process = start_ffmpeg()

    except WebSocketDisconnect:
        print(f"📴 WebSocket cerrado: {cam_id}")

    finally:
        if process.poll() is None:
            process.stdin.close()
            process.wait()
                    
@router.post("/update/{code}")
async def update_names(code: str, data: NameList):
    # Asegurarse de que el directorio exista
    #os.makedirs(os.path.join(APP_DIR, "temp-data"), exist_ok=True)
    dir_path = os.path.join(APP_DIR, "temp-data", code)
    os.makedirs(dir_path, exist_ok=True)
    fecha = datetime.now().strftime("%Y%m%d_%H%M%S")
    unique_id = uuid.uuid4().hex[:2]
    #filename = f"{code}.json"
    filename = f"{code}_{fecha}_{unique_id}.json"
    filepath = os.path.join(APP_DIR, "temp-data",code, filename)

    # Guardar la lista en el archivo
    with open(filepath, "w", encoding="utf-8") as f:
        json.dump(data.campaings, f, ensure_ascii=False, indent=2)
    token = await get_client_token()
    url_striming=BASE_STREAM_PATH / code
    response = await update_element(token,data,code)
    return {
        "campaings": data.campaings
    }
@router.get("/token")
async def haz_algo(Authorization: str = Header(...)):
    print(APP_DIR)
    return 1
    token = Authorization.split(" ")[1]
    decoded_token = validate_token(token)
    print(decoded_token)
    token = await get_client_token()
    # Ahora podés usar `token` para hacer peticiones a la otra API
    return {"token": token}
@app.get("/validate-token-streming")
async def validate_token_streming(token: str, file: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=403, detail="Token expirado")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=403, detail="Token inválido")

    # Evitar path traversal
    normalized_path = os.path.normpath(os.path.join(BASE_PATH, file))

    # Confirmar que el archivo está dentro del directorio streams (para seguridad)
    if not normalized_path.startswith(BASE_PATH):
        raise HTTPException(status_code=403, detail="Acceso no permitido")

    if not os.path.isfile(normalized_path):
        raise HTTPException(status_code=404, detail="Archivo no encontrado")

    return FileResponse(normalized_path)

@router.get("/streams-v2/{cam_id}/{filename}")
async def get_stream_file(cam_id: str, filename: str, token: str = Query(...)):
    decoded_token = validate_token(token)
    path = BASE_STREAM_PATH / cam_id / filename
    if not path.exists():
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail="Archivo no encontrado")
    return FileResponse(path)


@router.post("/v2/register")
async def register(
    background_tasks: BackgroundTasks,
    request: Request,
   # token: str = Form(...),
    address: str = Form(...),
    striming: bool = Form(False),
    country: Literal["Chile", "Mexico", "Peru"] = Form(...),
    code: str = Form(...),
    csr_file: UploadFile = Form(...),
    double_sided: bool = Form(False),
    db: Session = Depends(get_db)
):
    # 1. Validar token en DB
    """
    token_obj = db.query(InstallToken).filter_by(token=token).first()
    if not token_obj:
        raise HTTPException(status_code=404, detail="Token no encontrado")
    if token_obj.used:
        raise HTTPException(status_code=400, detail="Token ya fue usado")
    client_ip = request.client.host
    """
    # 2. Guardar CSR temporalmente
    uid = str(uuid.uuid4())
    csr_path = UPLOAD_DIR / f"{uid}.csr"
    crt_path = SIGNED_DIR / f"{uid}.crt"

    csr_content = await csr_file.read()
    with open(csr_path, "wb") as f:
        f.write(csr_content)

    # 3. Firmar CSR usando OpenSSL
    try:
        subprocess.run([
            "openssl", "x509", "-req",
            "-in", str(csr_path),
            "-CA", str(CA_CRT),
            "-CAkey", str(CA_KEY),
            "-CAcreateserial",
            "-out", str(crt_path),
            "-days", "365",
            "-sha256"
        ], check=True)
    except subprocess.CalledProcessError as e:
        raise HTTPException(status_code=500, detail=f"Error firmando CSR: {e}")
    """
    # 4. Marcar token como usado
    token_obj.used = True
    token_obj.ip = client_ip
    token_obj.element_code = code
    token_obj.used_at = datetime.utcnow()
    """
    #db.commit()
    token = await get_client_token()
    if not double_sided:
        #url_striming = f"{API_URL}/streams/{code}/{INDEX_STREMING}"
        if striming is True:
            url_striming=f"{API_URL}/streams/{code}/{INDEX_STREMING}"
        else:
            url_striming=None
        background_tasks.add_task(crear_elemento_def, token, code, address, url_striming, country)
        #response = await crear_elemento(token, code, address, url_striming, country)
        Path((BASE_STREAM_PATH) / code).mkdir(parents=True, exist_ok=True)

    else:
        for suffix in ["-1", "-2"]:
            full_code = f"{code}{suffix}"
            if striming is True:
                url_striming=f"{API_URL}/streams/{full_code}/{INDEX_STREMING}"
            else:
                url_striming=None
            background_tasks.add_task(crear_elemento_def, token, full_code, address, url_striming, country)
            #response = await crear_elemento(token, full_code, address, url_striming, country)
            Path((BASE_STREAM_PATH) / full_code).mkdir(parents=True, exist_ok=True)
    # 5. Leer certificados firmados y CA como texto PEM
    print (type(double_sided),type(url_striming))

    with open(crt_path, "r") as f:
        signed_cert_pem = f.read()
    with open(CA_CRT, "r") as f:
        ca_cert_pem = f.read()

    # 6. Devolver ambos certificados en JSON
    return JSONResponse(content={
        "client_cert": signed_cert_pem,
        "ca_cert": ca_cert_pem,
    })