package catalyst

import (
	"archive/zip"
	"bytes"
	"io"
	"io/fs"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/aws/aws-sdk-go/service/s3"

	"github.com/SecurityBrewery/catalyst/database"
	"github.com/SecurityBrewery/catalyst/generated/api"
	"github.com/SecurityBrewery/catalyst/storage"
)

func backupHandler(catalystStorage *storage.Storage, c *database.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Disposition", "attachment; filename=backup.zip")
		w.Header().Set("Content-Type", "application/zip")
		err := Backup(catalystStorage, c, w)
		if err != nil {
			api.JSONError(w, err)
		}
	}
}

type WriterAtBuffer struct {
	bytes.Buffer
}

func (fw WriterAtBuffer) WriteAt(p []byte, offset int64) (n int, err error) {
	return fw.Write(p)
}

func Backup(catalystStorage *storage.Storage, c *database.Config, writer io.Writer) error {
	archive := zip.NewWriter(writer)
	defer archive.Close()

	err := archive.SetComment(GetVersion())
	if err != nil {
		return err
	}

	// S3
	if err := backupS3(catalystStorage, archive); err != nil {
		return err
	}

	// Arango
	return backupArango(c, archive)
}

func backupS3(catalystStorage *storage.Storage, archive *zip.Writer) error {
	buckets, err := catalystStorage.S3().ListBuckets(nil)
	if err != nil {
		return err
	}

	for _, bucket := range buckets.Buckets {
		objects, err := catalystStorage.S3().ListObjectsV2(&s3.ListObjectsV2Input{
			Bucket: bucket.Name,
		})
		if err != nil {
			return err
		}

		for _, content := range objects.Contents {
			rbuf := &WriterAtBuffer{}
			_, err := catalystStorage.Downloader().Download(rbuf, &s3.GetObjectInput{
				Bucket: bucket.Name,
				Key:    content.Key,
			})
			if err != nil {
				return err
			}

			a, err := archive.Create(path.Join("minio", *bucket.Name, *content.Key))
			if err != nil {
				return err
			}

			if _, err := io.Copy(a, rbuf); err != nil {
				return err
			}
		}
	}

	return nil
}

func backupArango(c *database.Config, archive *zip.Writer) error {
	dir, err := os.MkdirTemp("", "catalyst-backup")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	if err := arangodump(dir, c); err != nil {
		return err
	}

	return zipDump(dir, archive)
}

func zipDump(dir string, archive *zip.Writer) error {
	fsys := os.DirFS(dir)

	return fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if d.IsDir() {
			return nil
		}

		a, err := archive.Create(path.Join("arango", p))
		if err != nil {
			return err
		}

		f, err := fsys.Open(p)
		if err != nil {
			return err
		}

		if _, err := io.Copy(a, f); err != nil {
			return err
		}

		return nil
	})
}

func arangodump(dir string, config *database.Config) error {
	host := strings.Replace(config.Host, "http", "tcp", 1)

	name := config.Name
	if config.Name == "" {
		name = database.Name
	}
	args := []string{
		"--output-directory", dir, "--server.endpoint", host,
		"--server.username", config.User, "--server.password", config.Password,
		"--server.database", name,
	}
	cmd := exec.Command("arangodump", args...)

	return cmd.Run()
}
