package main

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	gocontext "context"

	"github.com/docker/containerd/api/execution"
	execEvents "github.com/docker/containerd/execution"
	"github.com/docker/docker/pkg/term"
	"github.com/nats-io/go-nats"
	"github.com/urfave/cli"
)

type runConfig struct {
	Image   string `toml:"image"`
	Process struct {
		Args []string `toml:"args"`
		Env  []string `toml:"env"`
		Cwd  string   `toml:"cwd"`
		Uid  int      `toml:"uid"`
		Gid  int      `toml:"gid"`
		Tty  bool     `toml:"tty"`
	} `toml:"process"`
	Network struct {
		Type    string `toml:"type"`
		IP      string `toml:"ip"`
		Gateway string `toml:"gateway"`
	} `toml:"network"`
}

var runCommand = cli.Command{
	Name:  "run",
	Usage: "run a container",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "bundle, b",
			Usage: "path to the container's bundle",
		},
		cli.BoolFlag{
			Name:  "tty, t",
			Usage: "allocate a TTY for the container",
		},
	},
	Action: func(context *cli.Context) error {
		// var config runConfig
		// if _, err := toml.DecodeFile(context.Args().First(), &config); err != nil {
		// 	return err
		// }
		id := context.Args().First()
		if id == "" {
			return fmt.Errorf("container id must be provided")
		}
		executionService, err := getExecutionService(context)
		if err != nil {
			return err
		}

		// setup our event subscriber
		nc, err := nats.Connect(nats.DefaultURL)
		if err != nil {
			return err
		}
		nec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
		if err != nil {
			nc.Close()
			return err
		}
		defer nec.Close()

		evCh := make(chan *execEvents.ContainerExitEvent, 64)
		sub, err := nec.Subscribe(execEvents.ContainersEventsSubjectSubscriber, func(e *execEvents.ContainerExitEvent) {
			evCh <- e
		})
		if err != nil {
			return err
		}
		defer sub.Unsubscribe()

		tmpDir, err := getTempDir(id)
		if err != nil {
			return err
		}
		defer os.RemoveAll(tmpDir)

		bundle, err := filepath.Abs(context.String("bundle"))
		if err != nil {
			return err
		}
		crOpts := &execution.CreateContainerRequest{
			ID:         id,
			BundlePath: bundle,
			Console:    context.Bool("tty"),
			Stdin:      filepath.Join(tmpDir, "stdin"),
			Stdout:     filepath.Join(tmpDir, "stdout"),
			Stderr:     filepath.Join(tmpDir, "stderr"),
		}

		var oldState *term.State
		restoreTerm := func() {
			if oldState != nil {
				term.RestoreTerminal(os.Stdin.Fd(), oldState)
			}
		}

		if crOpts.Console {
			oldState, err = term.SetRawTerminal(os.Stdin.Fd())
			if err != nil {
				return err
			}
			defer restoreTerm()
		}

		fwg, err := prepareStdio(crOpts.Stdin, crOpts.Stdout, crOpts.Stderr, crOpts.Console)
		if err != nil {
			return err
		}

		cr, err := executionService.Create(gocontext.Background(), crOpts)
		if err != nil {
			return err
		}

		if _, err := executionService.Start(gocontext.Background(), &execution.StartContainerRequest{
			ID: cr.Container.ID,
		}); err != nil {
			return err
		}

		var ec uint32
	eventLoop:
		for {
			select {
			case e, more := <-evCh:
				if !more {
					break eventLoop
				}

				if e.ID == cr.Container.ID && e.PID == cr.InitProcess.ID {
					ec = e.StatusCode
					break eventLoop
				}
			case <-time.After(1 * time.Second):
				if nec.Conn.Status() != nats.CONNECTED {
					break eventLoop
				}
			}
		}

		if _, err := executionService.Delete(gocontext.Background(), &execution.DeleteContainerRequest{
			ID: cr.Container.ID,
		}); err != nil {
			return err
		}

		// Ensure we read all io
		fwg.Wait()

		restoreTerm()
		os.Exit(int(ec))

		return nil
	},
}