package web

import (
	"bytes"
	"encoding/base64"
	"io"
	"mime/multipart"
	"net/http"

	p "github.com/ajswis/go-pkparse-server/pokemon"
	pokemonparsing "github.com/ajswis/go-pkparse-server/pokemon-parsing"
	"github.com/gin-gonic/gin"
)

/*
	File uploads:
  curl -X POST http://localhost:8080/parse \
		-H "Content-Type: multipart/form-data" \
	  --form "pokemon=@/path/to/test/file1" \
	  --form "pokemon=@/path/to/test/file2"

	JSON POST:
  curl -X POST http://localhost:8080/parse \
		-H "Content-Type: application/json" \
		-d '{"pokemon": ["base64_encoded_pokemon_file", "another_base64_encoded_pokemon_file"]}'

	Form POST:
	curl -X POST http://localhost:8080/parse \
		-H "Content-Type: multipart/form-data" \
		--form 'pokemon=base64_encoded_pokemon_file' \
		--form 'pokemon=another_base64_encoded_pokemon_file'

	Form POST + File upload:
  curl -X POST http://localhost:8080/parse \
		-H "Content-Type: multipart/form-data" \
	  --form "pokemon=@/path/to/test/file1" \
	  --form "pokemon=@/path/to/test/file2" \
		--form 'pokemon=base64_encoded_pokemon_file' \
*/

type pokemonParseRequest struct {
	Pokemon []string `json:"pokemon" form:"pokemon"`
}

// parse grabs multipart file uploads under the key `pokemon`, aggregates them,
// and delegates work to the parser.
func (s *Server) parse(c *gin.Context) {
	var rawPokemon []p.RawPokemon

	// If the POST is `multipart/form-data`, check for file uploads and read them
	if c.ContentType() == "multipart/form-data" {
		fileHeaders, err := uploadedPokemonFiles(c)
		if err != nil {
			render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		if len(fileHeaders) > 0 {
			err = pokemonFromFileUpload(fileHeaders, &rawPokemon)
			if err != nil {
				render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
		}
	}

	// For non-file-upload form submissions or all other `Content-Type`s
	err := pokemonFromEncodedUpload(c, &rawPokemon)
	if err != nil {
		render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	pkmn, err := pokemonparsing.ParseAll(rawPokemon)
	if err != nil {
		render(c, http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
		return
	}

	render(c, http.StatusOK, pkmn)
}

// uploadedPokemonFiles extracts any existing FileHeaders from file uploads
func uploadedPokemonFiles(c *gin.Context) ([]*multipart.FileHeader, error) {
	form, err := c.MultipartForm()
	if err != nil {
		return nil, err
	}

	return form.File["pokemon"], nil
}

// pokemonFromFileUpload reads data from uploaded files and returns the raw
// pokemon
func pokemonFromFileUpload(files []*multipart.FileHeader, rawPokemon *[]p.RawPokemon) error {
	for _, file := range files {
		var err error
		var src multipart.File

		src, err = file.Open()
		if err != nil {
			return err
		}
		defer src.Close()

		buf := bytes.NewBuffer(nil)
		if _, err = io.Copy(buf, src); err != nil {
			return err
		}

		*rawPokemon = append(*rawPokemon, buf.Bytes())
	}

	return nil
}

// pokemonFromEncodedUpload binds the POST data to a `pokemonParseRequest` and
// converts each base64 string to a `[]byte` slice.
func pokemonFromEncodedUpload(c *gin.Context, rawPokemon *[]p.RawPokemon) error {
	var request pokemonParseRequest

	if err := c.ShouldBind(&request); err != nil {
		return err
	}

	for _, pkmn := range request.Pokemon {
		var err error
		var decoded []byte

		decoded, err = base64.StdEncoding.DecodeString(pkmn)

		if err != nil {
			return err
		}

		*rawPokemon = append(*rawPokemon, decoded)
	}

	return nil
}
