#!/usr/bin/env -S dotnet fsi
#r "nuget: MongoDB.Driver"
#r "nuget: MongoDB.Driver.GridFS"
#r "nuget: Mondocks.Net"
#r "nuget: Spectre.Console"

open System
open System.IO
open Spectre.Console
open MongoDB.Bson
open MongoDB.Driver
open MongoDB.Driver.GridFS
open Mondocks.Types
open Mondocks.Queries

let args = (fsi.CommandLineArgs)

// here we also start by parsing the cli args

let restoreTo =
    args
    |> Seq.tryPick (fun arg -> if arg.Contains("--restoreDir=") then Some arg else None)
    |> Option.map (fun arg -> arg.Replace("--restoreDir=", ""))

let dbArg =
    args
    |> Seq.tryPick (fun arg -> if arg.Contains("--database=") then Some arg else None)
    |> Option.map (fun arg -> arg.Replace("--database=", ""))

let restoreDir =
    match restoreTo with
    | None -> Directory.GetCurrentDirectory()
    | Some dir -> Path.GetFullPath(dir)

let dburl = 
    match dbArg with 
    | None -> "mongodb://localhost:27017"
    | Some url -> url

if not (Directory.Exists(restoreDir)) then 
    raise (exn "Couldn't set Current Directory")

let dir = DirectoryInfo(restoreDir)

// we'll use the same backup entry we used before,
// the only difference is that this includes the _id field
type BackupEntry = { _id: ObjectId; directory: string; backupAt: DateTime; filenames: string array; entryType: string }

type EntryType = 
    | Attempt
    | Success

    member this.AsString() =
        match this with 
        | Attempt -> "Attempt"
        | Success -> "Success"

    static member FromString(value: string) =
        match value with 
        | "Attempt" -> Attempt
        | "Success" -> Success
        | _ -> failwith "Unknown value"
    

let client = MongoClient(dburl)
let backupsdb = client.GetDatabase("backups")
let bucket = GridFSBucket(backupsdb)

let findBackupsCmd = 
    // here we do a find query using an anonymous object
    // and we'll filter by successful backup entries
    find "backups" {
        filter {| entryType = EntryType.Success.AsString() |}
    }
let backups = backupsdb.RunCommand<FindResult<BackupEntry>>(JsonCommand findBackupsCmd)

Console.Clear()

AnsiConsole.MarkupLine("[bold]Found the following backups[/]")
let tbl = Table()
tbl
    .AddColumn("Directory")
    .AddColumn("Backup Date")
    .AddColumn("Files")
    .AddColumn("Entry Type")
let found = backups.cursor.firstBatch |> Seq.sortByDescending(fun entry -> entry.backupAt)

found
|> Seq.iteri(fun index entry ->
    let date = $"{entry.backupAt.ToShortDateString()} - {entry.backupAt.ToShortTimeString()}"
    let files = $"{entry.filenames |> Array.length}"
    tbl.AddRow([| $"{index + 1} - {entry.directory}"; date; files; entry.entryType |]) |> ignore
   )

AnsiConsole.Render(tbl)

let prompt = 
    let txt = TextPrompt<int>("Select the desired backup")
    found 
        |> Seq.iteri(fun index backup -> 
            txt.AddChoice<int>(index + 1) |> ignore)
    txt
        .DefaultValue(1)
        // let's try to also validate that the answer
        // is within range of our existing backups
        .Validator <- fun value ->
            match value with 
            | value when value <= 0 || value >= (found |> Seq.length) -> ValidationResult.Error("The selected backup was not found")
            | result -> ValidationResult.Success()
    txt

let response = 
    (AnsiConsole
        .Prompt<int>(prompt)) - 1

let selected = backups.cursor.firstBatch |> Seq.item response

let filenames = selected.filenames

AnsiConsole
    .Progress()
    .Columns([|
        new SpinnerColumn()
        new PercentageColumn()
        new ProgressBarColumn()
        new TaskDescriptionColumn()
    |])
    .Start(fun ctx ->
        let settings = ProgressTaskSettings()
        settings.MaxValue <- filenames |> Seq.length |> float

        let task = ctx.AddTask($"Restoring files to %s{dir.FullName}", settings)
        for filename in filenames do
            // for every entry in our backup we create a file stream
            // thankfully MongoDB.Driver.GridFS includes a way to download
            // the files directly into a stream, so the process is quite seamless
            use filestr = File.Create(Path.Combine(dir.FullName, filename))
            bucket.DownloadToStreamByName(filename, filestr)
            task.Increment(1.0)
        task.StopTask()
    )
AnsiConsole.WriteLine($"Restored: %i{filenames |> Seq.length} files")