package cmd

import (
	"fmt"
	"net"
	"net/http"
	"os"
	"sort"
	"strings"

	"github.com/aptly-dev/aptly/aptly"
	"github.com/aptly-dev/aptly/deb"
	"github.com/aptly-dev/aptly/utils"
	"github.com/smira/commander"
	"github.com/smira/flag"
)

func aptlyServe(cmd *commander.Command, args []string) error {
	var err error

	if len(args) != 0 {
		cmd.Usage()
		return commander.ErrCommandError
	}

	// There are only two working options for aptly's rootDir:
	//   1. rootDir does not exist, then we'll create it
	//   2. rootDir exists and is writable
	// anything else must fail.
	// E.g.: Running the service under a different user may lead to a rootDir
	// that exists but is not usable due to access permissions.
	err = utils.DirIsAccessible(context.Config().GetRootDir())
	if err != nil {
		return err
	}

	collectionFactory := context.NewCollectionFactory()
	if collectionFactory.PublishedRepoCollection().Len() == 0 {
		fmt.Printf("No published repositories, unable to serve.\n")
		return nil
	}

	listen := context.Flags().Lookup("listen").Value.String()

	listenHost, listenPort, err := net.SplitHostPort(listen)

	if err != nil {
		return fmt.Errorf("wrong -listen specification: %s", err)
	}

	if listenHost == "" {
		listenHost, err = os.Hostname()
		if err != nil {
			listenHost = "localhost"
		}
	}

	fmt.Printf("Serving published repositories, recommended apt sources list:\n\n")

	sources := make(sort.StringSlice, 0, collectionFactory.PublishedRepoCollection().Len())
	published := make(map[string]*deb.PublishedRepo, collectionFactory.PublishedRepoCollection().Len())

	err = collectionFactory.PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
		e := collectionFactory.PublishedRepoCollection().LoadComplete(repo, collectionFactory)
		if e != nil {
			return e
		}

		sources = append(sources, repo.String())
		published[repo.String()] = repo

		return nil
	})

	if err != nil {
		return fmt.Errorf("unable to serve: %s", err)
	}

	sort.Strings(sources)

	for _, source := range sources {
		repo := published[source]

		prefix := repo.Prefix
		if prefix == "." {
			prefix = ""
		} else {
			prefix += "/"
		}

		fmt.Printf("# %s\ndeb http://%s:%s/%s %s %s\n",
			repo, listenHost, listenPort, prefix, repo.Distribution, strings.Join(repo.Components(), " "))

		if utils.StrSliceHasItem(repo.Architectures, deb.ArchitectureSource) {
			fmt.Printf("deb-src http://%s:%s/%s %s %s\n",
				listenHost, listenPort, prefix, repo.Distribution, strings.Join(repo.Components(), " "))
		}
	}

	publicPath := context.GetPublishedStorage("").(aptly.FileSystemPublishedStorage).PublicPath()
	ShutdownContext()

	fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)

	err = http.ListenAndServe(listen, http.FileServer(http.Dir(publicPath)))
	if err != nil {
		return fmt.Errorf("unable to serve: %s", err)
	}
	return nil
}

func makeCmdServe() *commander.Command {
	cmd := &commander.Command{
		Run:       aptlyServe,
		UsageLine: "serve",
		Short:     "HTTP serve published repositories",
		Long: `
Command serve starts embedded HTTP server (not suitable for real production usage) to serve
contents of public/ subdirectory of aptly's root that contains published repositories.

Example:

  $ aptly serve -listen=:8080
`,
		Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError),
	}

	cmd.Flag.String("listen", ":8080", "host:port for HTTP listening")

	return cmd
}
