package commands

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/howeyc/gopass"
	"github.com/writeas/writeas-cli/api"
	"github.com/writeas/writeas-cli/config"
	"github.com/writeas/writeas-cli/executable"
	"github.com/writeas/writeas-cli/log"
	cli "gopkg.in/urfave/cli.v1"
)

func CmdPost(c *cli.Context) error {
	if config.IsTor(c) {
		log.Info(c, "Publishing via hidden service...")
	} else {
		log.Info(c, "Publishing...")
	}

	_, err := api.DoPost(c, api.ReadStdIn(), c.String("font"), false, c.Bool("code"))
	if err != nil {
		return cli.NewExitError(err.Error(), 1)
	}
	return nil
}

func CmdNew(c *cli.Context) error {
	fname, p := api.ComposeNewPost()
	if p == nil {
		// Assume composeNewPost already told us what the error was. Abort now.
		os.Exit(1)
	}

	// Ensure we have something to post
	if len(*p) == 0 {
		// Clean up temporary post
		if fname != "" {
			os.Remove(fname)
		}

		log.InfolnQuit("Empty post. Bye!")
	}

	if config.IsTor(c) {
		log.Info(c, "Publishing via hidden service...")
	} else {
		log.Info(c, "Publishing...")
	}

	_, err := api.DoPost(c, *p, c.String("font"), false, c.Bool("code"))
	if err != nil {
		log.Errorln("Error posting: %s\n%s", err, config.MessageRetryCompose(fname))
		return cli.NewExitError("", 1)
	}

	// Clean up temporary post
	if fname != "" {
		os.Remove(fname)
	}

	return nil
}

func CmdPublish(c *cli.Context) error {
	filename := c.Args().Get(0)
	if filename == "" {
		return cli.NewExitError("usage: "+executable.Name()+" publish <filename>", 1)
	}
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}

	if config.IsTor(c) {
		log.Info(c, "Publishing via hidden service...")
	} else {
		log.Info(c, "Publishing...")
	}
	_, err = api.DoPost(c, content, c.String("font"), false, c.Bool("code"))
	if err != nil {
		return cli.NewExitError(err.Error(), 1)
	}

	// TODO: write local file if directory is set
	return nil
}

func CmdDelete(c *cli.Context) error {
	friendlyID := c.Args().Get(0)
	token := c.Args().Get(1)
	if friendlyID == "" {
		return cli.NewExitError("usage: "+executable.Name()+" delete <postId> [<token>]", 1)
	}

	u, _ := config.LoadUser(c)
	if token == "" {
		// Search for the token locally
		token = api.TokenFromID(c, friendlyID)
		if token == "" && u == nil {
			log.Errorln("Couldn't find an edit token locally. Did you create this post here?")
			log.ErrorlnQuit("If you have an edit token, use: "+executable.Name()+" delete %s <token>", friendlyID)
		}
	}

	if config.IsTor(c) {
		log.Info(c, "Deleting via hidden service...")
	} else {
		log.Info(c, "Deleting...")
	}

	err := api.DoDelete(c, friendlyID, token)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("Couldn't delete post: %v", err), 1)
	}

	// TODO: Delete local file, if necessary
	return nil
}

func CmdUpdate(c *cli.Context) error {
	friendlyID := c.Args().Get(0)
	token := c.Args().Get(1)
	if friendlyID == "" {
		return cli.NewExitError("usage: "+executable.Name()+" update <postId> [<token>]", 1)
	}

	u, _ := config.LoadUser(c)
	if token == "" {
		// Search for the token locally
		token = api.TokenFromID(c, friendlyID)
		if token == "" && u == nil {
			log.Errorln("Couldn't find an edit token locally. Did you create this post here?")
			log.ErrorlnQuit("If you have an edit token, use: "+executable.Name()+" update %s <token>", friendlyID)
		}
	}

	// Read post body
	fullPost := api.ReadStdIn()

	if config.IsTor(c) {
		log.Info(c, "Updating via hidden service...")
	} else {
		log.Info(c, "Updating...")
	}
	err := api.DoUpdate(c, fullPost, friendlyID, token, c.String("font"), c.Bool("code"))
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("%v", err), 1)
	}
	return nil
}

func CmdGet(c *cli.Context) error {
	friendlyID := c.Args().Get(0)
	if friendlyID == "" {
		return cli.NewExitError("usage: "+executable.Name()+" get <postId>", 1)
	}

	if config.IsTor(c) {
		log.Info(c, "Getting via hidden service...")
	} else {
		log.Info(c, "Getting...")
	}

	err := api.DoFetch(c, friendlyID)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("%v", err), 1)
	}
	return nil
}

func CmdAdd(c *cli.Context) error {
	friendlyID := c.Args().Get(0)
	token := c.Args().Get(1)
	if friendlyID == "" || token == "" {
		return cli.NewExitError("usage: "+executable.Name()+" add <postId> <token>", 1)
	}

	err := api.AddPost(c, friendlyID, token)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("%v", err), 1)
	}
	return nil
}

func CmdListPosts(c *cli.Context) error {
	urls := c.Bool("url")
	ids := c.Bool("id")
	details := c.Bool("v")

	posts := api.GetPosts(c)

	u, _ := config.LoadUser(c)
	if u != nil {
		if config.IsTor(c) {
			log.Info(c, "Getting posts via hidden service...")
		} else {
			log.Info(c, "Getting posts...")
		}
		remotePosts, err := api.GetUserPosts(c, true)
		if err != nil {
			return cli.NewExitError(fmt.Sprintf("error getting posts: %v", err), 1)
		}

		if len(remotePosts) > 0 {
			if c.App.Name == "wf" {
				fmt.Println("Draft Posts")
			} else {
				fmt.Println("Anonymous Posts")
			}
			if details {
				identifier := "URL"
				if ids || !urls {
					identifier = "ID"
				}
				fmt.Println(identifier)
			}
		}
		for _, p := range remotePosts {
			identifier := getPostURL(c, p.ID)
			if ids || !urls {
				identifier = p.ID
			}

			fmt.Println(identifier)
		}

		if len(*posts) > 0 {
			fmt.Printf("\nUnclaimed Posts\n")
		}
	}

	if details {
		var p api.Post
		tw := tabwriter.NewWriter(os.Stdout, 10, 0, 2, ' ', tabwriter.TabIndent)
		numPosts := len(*posts)
		if ids || !urls && numPosts != 0 {
			fmt.Fprintf(tw, "%s\t%s\t\n", "ID", "Token")
		} else if numPosts != 0 {
			fmt.Fprintf(tw, "%s\t%s\t\n", "URL", "Token")
		} else {
			fmt.Fprintf(tw, "No local posts found\n")
		}
		for i := range *posts {
			p = (*posts)[numPosts-1-i]
			if ids || !urls {
				fmt.Fprintf(tw, "%s\t%s\t\n", p.ID, p.EditToken)
			} else {
				fmt.Fprintf(tw, "%s\t%s\t\n", getPostURL(c, p.ID), p.EditToken)
			}
		}
		return tw.Flush()
	}

	for _, p := range *posts {
		if ids || !urls {
			fmt.Printf("%s\n", p.ID)
		} else {
			fmt.Printf("%s\n", getPostURL(c, p.ID))
		}
	}
	return nil
}

func getPostURL(c *cli.Context, slug string) string {
	var base string
	if c.App.Name == "writeas" {
		if config.IsDev() {
			base = config.DevBaseURL
		} else {
			base = config.WriteasBaseURL
		}
	} else {
		if host := api.HostURL(c); host != "" {
			base = host
		} else {
			// TODO handle error, or load config globally, see T601
			// https://phabricator.write.as/T601
			cfg, _ := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]))
			if cfg.Default.Host != "" && cfg.Default.User != "" {
				if parts := strings.Split(cfg.Default.Host, "://"); len(parts) > 1 {
					base = cfg.Default.Host
				} else {
					base = "https://" + cfg.Default.Host
				}
			}
		}
	}
	ext := ""
	// Output URL in requested format
	if c.Bool("md") {
		ext = ".md"
	}
	return fmt.Sprintf("%s/%s%s", base, slug, ext)
}

func CmdCollections(c *cli.Context) error {
	u, err := config.LoadUser(c)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("couldn't load config: %v", err), 1)
	}
	if u == nil {
		return cli.NewExitError("You must be authenticated to view collections.\nLog in first with: "+executable.Name()+" auth <username>", 1)
	}
	if config.IsTor(c) {
		log.Info(c, "Getting blogs via hidden service...")
	} else {
		log.Info(c, "Getting blogs...")
	}
	colls, err := api.DoFetchCollections(c)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("Couldn't get collections for user %s: %v", u.User.Username, err), 1)
	}
	urls := c.Bool("url")
	tw := tabwriter.NewWriter(os.Stdout, 8, 0, 2, ' ', tabwriter.TabIndent)
	detail := "Title"
	if urls {
		detail = "URL"
	}
	fmt.Fprintf(tw, "%s\t%s\t\n", "Alias", detail)
	for _, c := range colls {
		dData := c.Title
		if urls {
			dData = c.URL
		}
		fmt.Fprintf(tw, "%s\t%s\t\n", c.Alias, dData)
	}
	tw.Flush()
	return nil
}

func CmdClaim(c *cli.Context) error {
	u, err := config.LoadUser(c)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("couldn't load config: %v", err), 1)
	}
	if u == nil {
		return cli.NewExitError("You must be authenticated to claim local posts.\nLog in first with: "+executable.Name()+" auth <username>", 1)
	}

	localPosts := api.GetPosts(c)
	if len(*localPosts) == 0 {
		return nil
	}

	if config.IsTor(c) {
		log.Info(c, "Claiming %d post(s) for %s via hidden service...", len(*localPosts), u.User.Username)
	} else {
		log.Info(c, "Claiming %d post(s) for %s...", len(*localPosts), u.User.Username)
	}

	results, err := api.ClaimPosts(c, localPosts)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("Failed to claim posts: %v", err), 1)
	}

	var okCount, errCount int
	for _, r := range *results {
		id := r.ID
		if id == "" {
			// No top-level ID, so the claim was successful
			id = r.Post.ID
		}
		status := fmt.Sprintf("Post %s...", id)
		if r.ErrorMessage != "" {
			log.Errorln("%serror: %v", status, r.ErrorMessage)
			errCount++
		} else {
			log.Info(c, "%sOK", status)
			okCount++
			// only delete local if successful
			api.RemovePost(c, id)
		}
	}
	log.Info(c, "%d claimed, %d failed", okCount, errCount)
	return nil
}

func CmdAuth(c *cli.Context) error {
	username := c.Args().Get(0)
	if username == "" && c.GlobalIsSet("user") {
		username = c.GlobalString("user")
	}
	// Check configuration
	u, err := config.LoadUser(c)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("couldn't load config: %v", err), 1)
	}
	if u != nil && u.AccessToken != "" && username == u.User.Username {
		return cli.NewExitError("You're already authenticated as "+u.User.Username, 1)
	}

	// Validate arguments and get password
	if username == "" {
		cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]))
		if err != nil {
			return cli.NewExitError(fmt.Sprintf("Failed to load config: %v", err), 1)
		}
		if cfg.Default.Host != "" && cfg.Default.User != "" {
			username = cfg.Default.User
			fmt.Printf("No user provided, using default user %s for host %s...\n", cfg.Default.User, cfg.Default.Host)
		} else {
			return cli.NewExitError("usage: "+executable.Name()+" auth <username>", 1)
		}
	}

	// Take password from argument, and fall back to input
	pass := c.String("p")
	if pass == "" {
		fmt.Print("Password: ")
		enteredPass, err := gopass.GetPasswdMasked()
		if err != nil {
			return cli.NewExitError(fmt.Sprintf("error reading password: %v", err), 1)
		}

		// Validate password
		if len(enteredPass) == 0 {
			return cli.NewExitError("Please enter your password.", 1)
		}
		pass = string(enteredPass)
	}

	if config.IsTor(c) {
		log.Info(c, "Logging in via hidden service...")
	} else {
		log.Info(c, "Logging in...")
	}
	err = api.DoLogIn(c, username, pass)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("error logging in: %v", err), 1)
	}

	return nil
}

func CmdLogOut(c *cli.Context) error {
	if config.IsTor(c) {
		log.Info(c, "Logging out via hidden service...")
	} else {
		log.Info(c, "Logging out...")
	}
	err := api.DoLogOut(c)
	if err != nil {
		return cli.NewExitError(fmt.Sprintf("error logging out: %v", err), 1)
	}
	return nil
}
