package main

import (
  "fmt"
  "flag"
  "os"
  "bufio"
  "syscall"
  "strings"
  "golang.org/x/crypto/ssh/terminal"
  "dotty-cli/api"
)

func add(){
  mySet := flag.NewFlagSet("", flag.ExitOnError)
  file := mySet.String("f","", "File path")
  key := mySet.String("k", "", "File key")
  mySet.Parse(os.Args[2:])
  if *key == "" || *file == "" {
    fmt.Fprintf(os.Stderr, "Please specify at least a key and a file path.\n")
    os.Exit(1)
  }
  addErr := api.Add(key, file)
  if addErr != nil {
    fmt.Fprintf(os.Stderr, "Add failed: %s\n", addErr)
    os.Exit(1)
  }
  fmt.Printf("Added %s (%s)\n", *key, *file)
}

func remove(){
  mySet := flag.NewFlagSet("", flag.ExitOnError)
  key := mySet.String("k", "", "File key")
  mySet.Parse(os.Args[2:])
  if *key == "" {
    fmt.Fprintf(os.Stderr, "Please specify a key.\n")
    os.Exit(1)
  }
  remErr := api.Remove(key)
  if remErr != nil {
    fmt.Fprintf(os.Stderr, "Removal failed: %s\n", remErr)
    os.Exit(1)
  }
  fmt.Printf("Removed %s.\n", *key)
}

func push(){
  mySet := flag.NewFlagSet("", flag.ExitOnError)
  key := mySet.String("k", "", "File key")
  mySet.Parse(os.Args[2:])
  if *key == "" {
    fmt.Fprintf(os.Stderr, "Please specify a key name.\n")
    os.Exit(1)
  }
  pushErr := api.Push(key)
  if pushErr != nil {
    fmt.Fprintf(os.Stderr, "Push failed: %s\n", pushErr)
    os.Exit(1)
  }
  fmt.Printf("Pushed %s.\n", *key)
}

func pull(){
  mySet := flag.NewFlagSet("", flag.ExitOnError)
  key := mySet.String("k", "", "File key")
  mySet.Parse(os.Args[2:])
  if *key == "" {
    fmt.Fprintf(os.Stderr, "Please specify at least a key name.\n")
    os.Exit(1)
  }
  pullErr := api.Pull(key)
  if pullErr != nil {
    fmt.Fprintf(os.Stderr, "Pull failed: %s\n", pullErr)
    os.Exit(1)
  }
  fmt.Printf("Pulled %s.\n", *key)
}

func update(){
  updateErr := api.Update()
  if updateErr != nil {
    fmt.Fprintf(os.Stderr, "Update failed: %s\n", updateErr)
    os.Exit(1)
  }
  fmt.Println("Dotty registry updated successfully.")
}

func ls(){
  listErr := api.List()
  if listErr != nil {
    fmt.Fprintf(os.Stderr, "List failed: %s\n", listErr)
    os.Exit(1)
  }
}

func login(){
  reader := bufio.NewReader(os.Stdin)
  fmt.Print("Enter Email: ")
  email, _ := reader.ReadString('\n')
  fmt.Print("Enter Password: ")
  bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
  password := string(bytePassword)
  email = strings.TrimSpace(email)
  password = strings.TrimSpace(password)
  fmt.Printf("\n")

  loginErr := api.Login(&email, &password)
  if loginErr != nil {
    fmt.Fprintf(os.Stderr, "Login error: %s\n", loginErr)
    os.Exit(1)
  }
  fmt.Println("Login successful.")
}

func register(){
  reader := bufio.NewReader(os.Stdin)
  fmt.Print("Enter Email: ")
  email, _ := reader.ReadString('\n')
  email = strings.TrimSpace(email)
  fmt.Print("Enter Password: ")
  bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
  password := string(bytePassword)
  email = strings.TrimSpace(email)
  password = strings.TrimSpace(password)
  fmt.Printf("\n")

  err := api.Register(&email, &password)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Registration error: %s\n", err)
    os.Exit(1)
  }
  fmt.Println("Registration successful.")
}

func logout(){
  mySet := flag.NewFlagSet("", flag.ExitOnError)
  others := mySet.Bool("o", false, "Logout all OTHER sessions")
  all := mySet.Bool("a", false, "Logout ALL sessions")
  mySet.Parse(os.Args[2:])
  if *others && *all {
    fmt.Fprintf(os.Stderr, "Specify either -a, -o, or neither.")
    os.Exit(1)
  }
  var err error
  if *all {
    err = api.Logout("all")
  } else if *others {
    err = api.Logout("others")
  } else {
    err = api.Logout("")
  }
  if err != nil {
    fmt.Fprintf(os.Stderr, "Logout error: %s\n", err)
    os.Exit(1)
  }
  fmt.Println("Logout successful.")
}

func changePassword(){
  mySet := flag.NewFlagSet("", flag.ExitOnError)
  withToken := mySet.Bool("t", false, "Indicate a password reset with token")
  mySet.Parse(os.Args[2:])
  if *withToken {
    fmt.Print("Paste reset token from your email: ")
    byteToken, _ := terminal.ReadPassword(int(syscall.Stdin))
    token:= strings.TrimSpace(string(byteToken))
    fmt.Print("\nEnter new password: ")
    byteNewPassword, _ := terminal.ReadPassword(int(syscall.Stdin))
    newPassword := strings.TrimSpace(string(byteNewPassword))
    fmt.Printf("\n")

    err := api.ResetPassword(&token, &newPassword)
    if err != nil {
      fmt.Fprintf(os.Stderr, "Account update error: %s", err)
      os.Exit(1)
    }
    fmt.Println("Password reset successful. If you now need to login run 'dotty login'.")

  } else{
    fmt.Print("Enter current password: ")
    byteCurrentPassword, _ := terminal.ReadPassword(int(syscall.Stdin))
    currentPassword := strings.TrimSpace(string(byteCurrentPassword))
    fmt.Print("\nEnter new password: ")
    byteNewPassword, _ := terminal.ReadPassword(int(syscall.Stdin))
    newPassword := strings.TrimSpace(string(byteNewPassword))
    fmt.Printf("\n")

    err := api.ChangePassword(&currentPassword, &newPassword)
    if err != nil {
      fmt.Fprintf(os.Stderr, "Account update error: %s", err)
      os.Exit(1)
    }
    fmt.Println("Password change successful.")
  }
}

func resetPassword(){
  reader := bufio.NewReader(os.Stdin)
  fmt.Print("Enter email address: ")
  email, _ := reader.ReadString('\n')
  email = strings.TrimSpace(email)

  err := api.SendPasswordResetEmail(&email)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Password reset error: %s", err)
    os.Exit(1)
  }
  fmt.Println("If the account exists, instructions have been sent to the account-holder's email address.")
}

func printHelp(){
  fmt.Println("\nDotty commands:\n")

  fmt.Println("register  \t Create a new Dotty account.")
  fmt.Println("\t\t Dotty will ask for an email address and a password to create your account.")

  fmt.Println("\nlogin     \t Authenticate this machine with your Dotty account.")
  fmt.Println("\t\t Dotty will ask for an email and password to login.")

  fmt.Println("\nlogout    \t Disassociate this machine from your Dotty account.")
  fmt.Println("\t\t You will need to register or login before using Dotty again.")

  fmt.Println("\nreset-password \t Request a password-reset email for your Dotty account.")
  fmt.Println("\t\t Dotty will ask for the email address of a registered account.")

  fmt.Println("\nchange-password [-t]")
  fmt.Println("\t\t Update the password for your account.")
  fmt.Println("\t\t Default behaviour is to change the password for the logged-in account.")
  fmt.Println("\t\t If -t is specified, then supply a token from a password-reset email.")
  fmt.Println("\t\t Ex: dotty change-password (change current user's password)")
  fmt.Println("\t\t Ex: dotty change-password -t (change password after using 'reset-password')")

  fmt.Println("\nadd -k -f \t Add a new file to your account and push it.")
  fmt.Println("\t\t Fails if specified key name already exists.")
  fmt.Println("\t\t Ex: dotty add -k vimrc -f ~/.vimrc")

  fmt.Println("\npush -k \t Push an update to an existing file.")
  fmt.Println("\t\t Fails if key hasn't yet been added or not locally updated.")
  fmt.Println("\t\t Ex: dotty push -k vimrc")

  fmt.Println("\npull -k \t Pull the latest version of the file from Dotty.")
  fmt.Println("\t\t Fails if key hasn't yet been added or not locally updated.")
  fmt.Println("\t\t Ex: dotty pull -k vimrc")

  fmt.Println("\nupdate  \t Update local configuration with latest set of files from Dotty.")
  fmt.Println("\t\t Use if added a file from a different machine. Will prompt for new local paths.")
  fmt.Println("\t\t Ex: dotty update")

  fmt.Println("\nls      \t List all files stored in your Dotty account.")
  fmt.Println("\t\t List includes files not yet pulled to the local machine.")
  fmt.Println("\t\t Ex: dotty ls")

  fmt.Println("\nrm -k   \t Remove the specified file from your Dotty account.")
  fmt.Println("\t\t This will not remove any files from your local filesystem.")
  fmt.Println("\t\t Ex: dotty rm -k vimrc")

  fmt.Println("\nhelp     \t Print this help message.")
}

func main() {
  if len(os.Args) < 2{
    fmt.Fprintf(os.Stderr, "Please specify a command.")
    os.Exit(1)
  }

  command := os.Args[1]
  switch command {
    case "add":
      add()
    case "push":
      push()
    case "pull":
      pull()
    case "update":
      update()
    case "ls":
      ls()
    case "rm":
      remove()
    case "login":
      login()
    case "register":
      register()
    case "logout":
      logout()
    case "change-password":
      changePassword()
    case "reset-password":
      resetPassword()
    case "help":
      printHelp()
    default:
      fmt.Fprintf(os.Stderr, "Invalid Dotty command: %s", command)
      printHelp()
      os.Exit(1)
  }
}
