import LeanIO
open Std Async
open Std Http Server
open LeanIO
open LeanIO.Router
open LeanIO.Middlewares
open Lean
open Std

structure MediaEntry where
  name : String
  size : UInt64
  url  : String
deriving ToJson

structure Comment where
  id     : Nat
  video  : String
  author : String
  text   : String
deriving ToJson, FromJson

structure CreateComment where
  author : String
  text   : String
deriving FromJson

structure CommentStore where
  ref : IO.Ref (Array Comment)
deriving TypeName

private def commentMiddleware := do
  let ref ← IO.mkRef (#[] : Array Comment)
  return withExtension CommentStore { ref }

instance : FromRequestParts CommentStore where
  from_request_parts req :=
    match req.extensions.get CommentStore with
    | some s => .ok s
    | none => .error (.io_error "comment store not installed")

private def staticDir : System.FilePath := "Examples/LeanPlay/static"

def listVideos := GET "/videos" => do
  let entries : Array IO.FS.DirEntry ← System.FilePath.readDir (staticDir / "media")
  let mut videos := #[]
  for entry in entries do
    let path := entry.path
    let mdata ← path.metadata
    let url := s!"/media/{entry.fileName}"
    videos := videos.push { name := entry.fileName, size := mdata.byteSize, url : MediaEntry }
  return videos

def getComments := GET "/videos/{name}/comments" (⟨name⟩ : Path String) (⟨ref⟩ : CommentStore) => do
  let cs : Array Comment ← ref.get
  return cs.filter (fun (c : Comment) => c.video == name)

def postComment := POST "/videos/{name}/comments"
    (⟨body⟩ : Json CreateComment) (⟨name⟩ : Path String) (⟨ref⟩ : CommentStore) => do
  let cs : Array Comment ← ref.get
  let id := cs.size + 1
  let comment : Comment := { id, video := name, author := body.author, text := body.text }
  ref.set (cs.push comment)
  return (Status.created, comment)

def index := GET "/" => do
  return { path := staticDir / "index.html" : File }

def serveStatic := GET "/{*rest}" (⟨_⟩ : Path String) (p : URI.Path) => do
  let decoded := String.intercalate "/" (p.toDecodedSegments.toList)
  return { path := staticDir / decoded : RangeFile }

def main : IO Unit := Async.block do
  let apiRouter : Router := Router.empty
    |>.addRoute listVideos
    |>.addRoute getComments
    |>.addRoute postComment
    |>.addMiddleware catchErrors

  let router : Router := Router.empty
    |>.addRouter "/api/v1" apiRouter
    |>.addRoute index
    |>.addRoute serveStatic
    |>.addMiddleware (← commentMiddleware)
    |>.addMiddleware requestLogger
    |>.addMiddleware catchErrors

  let addr : Net.SocketAddress := .v4 ⟨.ofParts 127 0 0 1, 8080⟩
  let server ← router.serve addr
  IO.println "LeanPlay running at http://127.0.0.1:8080"
  server.waitShutdown
