// Package download handles the API call to download an osu! beatmap set.
package download

import (
	"fmt"
	"io"
	"log"
	"strconv"
	"time"

	"github.com/osuripple/cheesegull/api"
	"github.com/osuripple/cheesegull/downloader"
	"github.com/osuripple/cheesegull/housekeeper"
	"github.com/osuripple/cheesegull/models"
)

func errorMessage(c *api.Context, code int, err string) {
	c.WriteHeader("Content-Type", "text/plain; charset=utf-8")
	c.Code(code)
	c.Write([]byte(err))
}

func existsQueryKey(c *api.Context, s string) bool {
	_, ok := c.Request.URL.Query()[s]
	return ok
}

// Download is the handler for a request to download a beatmap
func Download(c *api.Context) {
	// get the beatmap ID
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		errorMessage(c, 400, "Malformed ID")
		return
	}

	// fetch beatmap set and make sure it exists.
	set, err := models.FetchSet(c.DB, id, false)
	if err != nil {
		c.Err(err)
		errorMessage(c, 500, "Could not fetch set")
		return
	}
	if set == nil {
		errorMessage(c, 404, "Set not found")
		return
	}

	// use novideo only when we are requested to get a beatmap having a video
	// and novideo is in the request
	noVideo := set.HasVideo && existsQueryKey(c, "novideo")

	cbm, shouldDownload := c.House.AcquireBeatmap(&housekeeper.CachedBeatmap{
		ID:         id,
		NoVideo:    noVideo,
		LastUpdate: set.LastUpdate,
	})

	if shouldDownload {
		err := downloadBeatmap(c.DLClient, cbm, c.House)
		if err != nil {
			c.Err(err)
			errorMessage(c, 500, "Internal error")
			return
		}
	} else {
		cbm.MustBeDownloaded()
	}

	cbm.SetLastRequested(time.Now())

	if cbm.FileSize() == 0 {
		errorMessage(c, 504, "The beatmap could not be downloaded (probably got deleted from the osu! website)")
		return
	}

	f, err := cbm.File()
	if err != nil {
		c.Err(err)
		errorMessage(c, 500, "Internal error")
		return
	}
	defer f.Close()

	c.WriteHeader("Content-Type", "application/octet-stream")
	c.WriteHeader("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fmt.Sprintf("%d %s - %s.osz", set.ID, set.Artist, set.Title)))
	c.WriteHeader("Content-Length", strconv.FormatUint(uint64(cbm.FileSize()), 10))
	c.Code(200)

	_, err = io.Copy(c, f)
	if err != nil {
		c.Err(err)
	}
}

func downloadBeatmap(c *downloader.Client, b *housekeeper.CachedBeatmap, house *housekeeper.House) error {
	log.Println("[⬇️]", b.String())

	var fileSize uint64
	defer func() {
		// We need to wrap this inside a function because this way the arguments
		// to DownloadCompleted are actually evaluated during the defer call.
		b.DownloadCompleted(fileSize, house)
	}()

	// Start downloading.
	r, err := c.Download(b.ID, b.NoVideo)
	if err != nil {
		if err == downloader.ErrNoRedirect {
			return nil
		}
		return err
	}
	defer r.Close()

	// open the file we will write the beatmap into
	f, err := b.CreateFile()
	if err != nil {
		return err
	}
	defer f.Close()

	fSizeRaw, err := io.Copy(f, r)
	fileSize = uint64(fSizeRaw)
	if err != nil {
		return err
	}
	return nil
}

func init() {
	api.GET("/d/:id", Download)
}
