import { spawn, type ChildProcessWithoutNullStreams } from "child_process";
import type { Request, Response, Router } from "express";
import ffmpegPath from "ffmpeg-static";
import fs from "fs";
import path from "path";


const SESSIONS_ROOT =
  path.resolve(process.cwd(), "hls-sessions");


const AUDIO_ROOT =
  "/home/divinoso/spot.onfide.com.br/app/uploads";


const IDLE_TIMEOUT_MS = 90000;
const MAX_SESSOES = 5;



type Sessao = {

  ffmpeg: ChildProcessWithoutNullStreams;

  dir:string;

  playlist:string;

  timer:NodeJS.Timeout|null;

  encerrando:boolean;

};



const sessoes =
  new Map<string,Sessao>();




function idValido(id:string){

  return /^[a-zA-Z0-9-]{6,64}$/.test(id);

}





function resolverArquivo(url:string){

  if(!url.startsWith("/uploads/"))
    return null;


  const relativo =
    url.replace("/uploads/","");


  const arquivo =
    path.resolve(
      AUDIO_ROOT,
      relativo
    );


  if(!arquivo.startsWith(AUDIO_ROOT))
    return null;


  return arquivo;

}





function escreverPlaylist(
  playlist:string,
  arquivo:string
){

  fs.appendFileSync(
    playlist,
`file '${arquivo.replace(/'/g,"'\\''")}'
`,
    "utf8"
  );

}





function iniciarFFmpeg(
  id:string,
  dir:string,
  playlist:string
): ChildProcessWithoutNullStreams {

  const ffmpeg = spawn(
    ffmpegPath!,
    [
      "-re",

      "-f",
      "concat",

      "-safe",
      "0",

      "-i",
      playlist,

      "-c:a",
      "aac",

      "-b:a",
      "128k",

      "-f",
      "hls",

      "-hls_time",
      "6",

      "-hls_list_size",
      "6",

      "-hls_flags",
      "delete_segments+append_list",

      "-hls_segment_filename",
      path.join(
        dir,
        "seg%05d.ts"
      ),

      path.join(
        dir,
        "live.m3u8"
      )
    ]
  ) as ChildProcessWithoutNullStreams;


  ffmpeg.stderr.on(
    "data",
    data => {
      console.log(
        "[FFMPEG]",
        data.toString()
      );
    }
  );


  return ffmpeg;
}






function criarSessao(
 id:string,
 primeiroArquivo:string
){


 if(!ffmpegPath)
   return null;



 if(sessoes.size>=MAX_SESSOES)
   return null;




 const dir =
 path.join(
   SESSIONS_ROOT,
   id
 );



 fs.mkdirSync(
   dir,
   {
    recursive:true
   }
 );



 const playlist =
 path.join(
   dir,
   "playlist.txt"
 );



 fs.writeFileSync(
   playlist,
   ""
 );



 escreverPlaylist(
   playlist,
   primeiroArquivo
 );



 const ffmpeg =
 iniciarFFmpeg(
   id,
   dir,
   playlist
 );



 const sessao:Sessao={

   ffmpeg,

   dir,

   playlist,

   timer:null,

   encerrando:false

 };



 ffmpeg.on(
   "close",
   ()=>{

    sessoes.delete(id);

    console.log(
      "[HLS] sessão encerrada",
      id
    );

   }
 );



 sessoes.set(
   id,
   sessao
 );



 return sessao;

}





function atividade(id:string){

 const s =
 sessoes.get(id);

 if(!s)
   return;


 if(s.timer)
   clearTimeout(s.timer);



 s.timer =
 setTimeout(
   ()=>encerrar(id),
   IDLE_TIMEOUT_MS
 );


}





function encerrar(id:string){

 const s =
 sessoes.get(id);


 if(!s)
   return;


 s.encerrando=true;


 try{

  s.ffmpeg.kill();

 }catch{}



 sessoes.delete(id);



 fs.rm(
   s.dir,
   {
    recursive:true,
    force:true
   },
   ()=>{}
 );


}






export function registerSessionStreamRoutes(
 app:Router
){



 fs.mkdirSync(
   SESSIONS_ROOT,
   {
    recursive:true
   }
 );





 app.post(
 "/api/session-stream/:id/acrescentar",
 (
 req:Request,
 res:Response
 )=>{


 const id =
 req.params.id;



 if(!idValido(id)){

  res.status(400).json({
   error:"sessão inválida"
  });

  return;

 }



 const url =
 typeof req.body?.arquivo==="string"
 ? req.body.arquivo
 : "";



 const arquivo =
 resolverArquivo(url);



 if(!arquivo){

  res.status(400).json({
   error:"arquivo inválido"
  });

  return;

 }



 if(!fs.existsSync(arquivo)){

  res.status(404).json({
   error:"arquivo não existe"
  });

  return;

 }



 let sessao =
 sessoes.get(id);



 if(!sessao){

   sessao =
   criarSessao(
     id,
     arquivo
   )
   ??
   undefined;


 }else{


   escreverPlaylist(
     sessao.playlist,
     arquivo
   );


 }



 if(!sessao){

   res.status(503).end();

   return;

 }



 atividade(id);



 res.status(202).json({
  ok:true
 });



 });



 



 app.get(
 "/session-stream/:id/live.m3u8",
 (
 req:Request,
 res:Response
 )=>{


 const sessao =
 sessoes.get(
   req.params.id
 );


 if(!sessao){

  res.status(404).end();

  return;

 }



 atividade(
   req.params.id
 );



 const arquivo =
 path.join(
   sessao.dir,
   "live.m3u8"
 );



 if(!fs.existsSync(arquivo)){

  res.status(503)
  .send(
   "aguardando"
  );

  return;

 }



 res.set(
 "Content-Type",
 "application/vnd.apple.mpegurl"
 );


 res.set(
 "Cache-Control",
 "no-store"
 );


 res.sendFile(
 arquivo
 );


 });






 app.get(
 "/session-stream/:id/:arquivo",
 (
 req:Request,
 res:Response
 )=>{


 const sessao =
 sessoes.get(
   req.params.id
 );


 if(!sessao){

  res.status(404).end();

  return;

 }



 if(!/^seg\d+\.ts$/.test(req.params.arquivo)){

  res.status(400).end();

  return;

 }



 const arquivo =
 path.join(
  sessao.dir,
  req.params.arquivo
 );



 if(!fs.existsSync(arquivo)){

  res.status(404).end();

  return;

 }



 atividade(
  req.params.id
 );



 res.set(
 "Content-Type",
 "video/mp2t"
 );


 res.sendFile(
  arquivo
 );


 });






 app.post(
 "/api/session-stream/:id/encerrar",
 (
 req:Request,
 res:Response
 )=>{


 encerrar(
  req.params.id
 );


 res.status(204).end();


 });


}