package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/signal"

	"gopkg.in/urfave/cli.v1"

	"ur.gs/crockery/internal/services"
	"ur.gs/crockery/internal/store"
)

func main() {
	app := cli.NewApp()
	app.EnableBashCompletion = true
	app.Name = "crockery"
	app.Usage = "A vertically integrated, single-domain email system"
	app.Authors = []cli.Author{
		{Name: "Nick Thomas", Email: "crockery@ur.gs"},
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "db",
			Usage:  "Database file to use",
			EnvVar: "CROCKERY_DB",
			Value:  "crockery.db",
		},
	}

	app.Commands = []cli.Command{
		{
			Name:   "init",
			Usage:  "Create a new Crockery database",
			Action: crockeryInit,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "domain",
					Usage:  "Domain to serve email for",
					EnvVar: "CROCKERY_DOMAIN",
				},
				cli.StringFlag{
					Name:   "cert",
					Usage:  "File containing the PEM-encoded certificate bundle for the domain",
					EnvVar: "CROCKERY_CERT_FILE",
				},

				cli.StringFlag{
					Name:   "key",
					Usage:  "File containing the PEM-encoded private key for the domain",
					EnvVar: "CROCKERY_KEY_FILE",
				},
			},
		},
		{
			Name:   "run",
			Usage:  "Run crockery",
			Action: crockeryRun,
		},
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}

func crockeryInit(c *cli.Context) error {
	db := c.GlobalString("db")
	domain := c.String("domain")
	certFile := c.String("cert")
	keyFile := c.String("key")

	if db == "" {
		return fmt.Errorf("No crockery database file specified")
	}

	if domain == "" {
		return fmt.Errorf("A domain must be specified")
	}

	// FIXME: we can make cert+key optional at some point and have them be
	// generated on-demand via ACME during `crockery run` instead.
	if certFile == "" {
		return fmt.Errorf("A certificate file must be specified")
	}

	if keyFile == "" {
		return fmt.Errorf("A key file must be specified")
	}

	if _, err := os.Stat(db); !os.IsNotExist(err) {
		return fmt.Errorf("File %q already exists, refusing to overwrite", db)
	}

	certPEM, err := ioutil.ReadFile(certFile)
	if err != nil {
		return fmt.Errorf("Failed to read certificate from %q: %v", certFile, err)
	}

	keyPEM, err := ioutil.ReadFile(keyFile)
	if err != nil {
		return fmt.Errorf("Failed to read key from %q: %v", keyFile, err)
	}

	if err := store.Init(db, domain, certPEM, keyPEM); err != nil {
		return fmt.Errorf("Couldn't initialize datase %q: %v", db, err)
	}

	log.Printf("Created %q for domain %q. Next step:\n\t%s -db %s run", db, domain, os.Args[0], db)

	return nil
}

func crockeryRun(c *cli.Context) error {
	db := c.GlobalString("db")
	ctx, cancel := context.WithCancel(context.Background())

	if db == "" {
		return fmt.Errorf("No crockery database file specified")
	}

	log.Printf("Loading config from file %q", db)

	datastore, err := store.New(context.Background(), db)
	if err != nil {
		return err
	}

	defer datastore.Close()

	if datastore.Domain() == "" {
		return fmt.Errorf("No domain configured in file %q", db)
	}

	log.Printf("Starting crockery for domain %q", datastore.Domain())

	srv, err := services.New(ctx, datastore)
	if err != nil {
		return fmt.Errorf("Couldn't start services:", err)
	}

	sig := make(chan os.Signal, 1)
	done := make(chan bool)

	signal.Notify(sig, os.Interrupt)

	go func() {
		s := <-sig
		log.Print("Got signal: ", s)
		cancel()
	}()

	go func() {
		srv.Run()
		close(done)
	}()

	<-done

	log.Println("All services finished, exiting")

	return nil
}
