/*
 * OpenAPI Petstore
 *
 * This is a sample of Petstore application. For this sample, you can use the api key `special-key` to test the authorization filters.
 *
 * API version: 1.0.0
 * Generated by: OpenAPI Generator (https://openapi-generator.tech)
 */

package openapi

import (
	"github.com/gin-gonic/gin"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"slices"
	"strconv"
	"strings"
)

type PetAPI struct {
	Pets map[int64]Pet
}

func (api *PetAPI) InitializePetsMap() {
	if api.Pets == nil {
		api.Pets = make(map[int64]Pet)
	}
}

// Post /v2/pet
// Add a new pet to the store 
func (api *PetAPI) AddPet(c *gin.Context) {
	api.InitializePetsMap()

	// first check the authorization headers
	authHeader := c.GetHeader("authorization")
	b64encoded, ok := strings.CutPrefix(authHeader, "Basic ")
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "No authorization header"})
		return
	}
	data, err := base64.StdEncoding.DecodeString(b64encoded)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Failed to decode authorization header"})
		return
	}
	decodedStr := string(data)
	nameAndPassword := strings.Split(decodedStr, ":")
	if len(nameAndPassword) != 2 || nameAndPassword[0] != "User1" || nameAndPassword[1] != "1234" {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Incorrect credentials"})
		return
	}

	var requestPet Pet
	if err := c.ShouldBindJSON(&requestPet); err != nil {
		fmt.Println("AddPet: failed to parse pet", err)
		c.JSON(http.StatusMethodNotAllowed, gin.H{"error": err.Error()})
		return
	}

	api.Pets[requestPet.Id] = requestPet

	c.JSON(http.StatusOK, requestPet)
}

// Delete /v2/pet/:petId
// Deletes a pet 
func (api *PetAPI) DeletePet(c *gin.Context) {
	api.InitializePetsMap()

	// This test also checks authorization
	// apiKeyHeader := c.GetHeader("api-key") // unused in the test!
	authHeader := c.GetHeader("authorization")
	if authHeader != "Bearer BEARER-TOKEN" {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Incorrect bearer token"})
		return
	}

	idStr := c.Param("petId")
	petId, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse pet id"})
		return
	}

	delete(api.Pets, petId)

	c.JSON(http.StatusOK, gin.H{"status": "OK"})
}

// Get /v2/pet/findPetsByAgeAndPatience/:petData
// get a pet by age and patience 
func (api *PetAPI) FindPetsByAgeAndPatience(c *gin.Context) {
	api.InitializePetsMap()

	dataStr := c.Param("petData")

	// dataStr has the following format:
	// "100,9,33,0"
	// Here, the first value is age, the second is patience, and so on.
	dataArr := strings.Split(dataStr, ",")
	total := len(dataArr)
	if total % 2 != 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Incorrect parameter format"})
		return
	}

	var pets []Pet
	for i := 0; i < total; i = i + 2 {
		val, _ := strconv.ParseInt(dataArr[i], 10, 32)
		age := int32(val)

		val, _ = strconv.ParseInt(dataArr[i + 1], 10, 32)
		patience := int32(val)

		for _, pet := range api.Pets {
			if pet.Age == age && pet.Patience == patience {
				pets = append(pets, pet)
			}
		}
	}

	c.JSON(http.StatusOK, pets)
}

// Get /v2/pet/findByStatus
// Finds Pets by status 
func (api *PetAPI) FindPetsByStatus(c *gin.Context) {
	api.InitializePetsMap()

	m, err := url.ParseQuery(c.Request.URL.RawQuery)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to parse query"})
		return
	}
	// The map contains desired statuses, e.g.
	// map[status:[available,sold]]
	// Note that "available,sold" is a single entry, we need to manually split it
	s, ok := m["status"]
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no statuses provided"})
		return
	}
	statuses := strings.Split(s[0], ",")

	var foundPets []Pet
	for _, pet := range api.Pets {
		if slices.Contains(statuses, string(pet.Status)) {
			foundPets = append(foundPets, pet)
		}
	}

	c.JSON(http.StatusOK, foundPets)
}

// Get /v2/pet/findByTags
// Finds Pets by tags 
// Deprecated
func (api *PetAPI) FindPetsByTags(c *gin.Context) {
	// Apparently not tested...
	c.JSON(http.StatusOK, gin.H{"status": "OK"})
}

// Get /v2/pet/findPetsImageById
// get a pet png image 
func (api *PetAPI) FindPetsImageById(c *gin.Context) {
	api.InitializePetsMap()

	m, err := url.ParseQuery(c.Request.URL.RawQuery)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to parse query"})
		return
	}

	ids, ok := m["petId"]
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no petId parameter"})
		return
	}

	petId, err := strconv.ParseInt(ids[0], 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to parse petId"})
		return
	}

	_, ok = api.Pets[petId]
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "pet not found"})
		return
	}

	bytes, _ := os.ReadFile("./qt-logo.png")
	b64encoded := base64.StdEncoding.EncodeToString(bytes)

	c.Header("Content-Type", "img/png")
	c.String(http.StatusOK, b64encoded)
}

// Get /v2/pet/:petId/uploadImage
// get a pet json information file 
func (api *PetAPI) GetJsonFile(c *gin.Context) {
	api.InitializePetsMap()

	idStr := c.Param("petId")
	petId, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse pet id"})
		return
	}

	_, ok := api.Pets[petId]
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "pet not found"})
		return
	}

	// We could use FileAttachement(), but it adds extra quotes for the filename
	c.Header("Content-Disposition", "attachment; filename=response.json")
	c.Header("Content-Type", "application/octet-stream")
	c.File("./response.json")

	c.Status(http.StatusOK)
}

// Get /v2/pet/:petId
// Find pet by ID 
func (api *PetAPI) GetPetById(c *gin.Context) {
	api.InitializePetsMap()

	idStr := c.Param("petId")
	petId, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse pet id"})
		return
	}

	pet, ok := api.Pets[petId]
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "pet not found"})
		return
	}

	c.JSON(http.StatusOK, pet)
}

// Put /v2/pet
// Update an existing pet 
func (api *PetAPI) UpdatePet(c *gin.Context) {
	api.InitializePetsMap()

	var requestPet Pet
	if err := c.ShouldBindJSON(&requestPet); err != nil {
		fmt.Println("UpdatePet: failed to parse pet", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// The C++ server didn't check if the pet exists or not, so we
	// won't either
	api.Pets[requestPet.Id] = requestPet

	c.JSON(http.StatusOK, requestPet)
}

// Post /v2/pet/:petId
// Updates a pet in the store with form data 
func (api *PetAPI) UpdatePetWithForm(c *gin.Context) {
	api.InitializePetsMap()

	idStr := c.Param("petId")
	petId, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse pet id"})
		return
	}

	pet, ok := api.Pets[petId]
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "pet not found"})
		return
	}

	// Both name and status are optional parameters!
	newName, ok := c.GetPostForm("name")
	if ok {
		pet.Name = newName
	}

	newStatus, ok := c.GetPostForm("status")
	if ok {
		pet.Status = PetStatus(newStatus)
	}

	api.Pets[petId] = pet

	c.JSON(http.StatusOK, gin.H{"status": "OK"})
}

// Post /v2/pet/:petId/uploadImage
// uploads an image 
func (api *PetAPI) UploadFile(c *gin.Context) {
	api.InitializePetsMap()

	idStr := c.Param("petId")
	petId, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse pet id"})
		return
	}

	_, ok := api.Pets[petId]
	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "pet not found"})
		return
	}

	// The C++ server returns a default-constructed ApiResponse object,
	// but we'd like to do better.
	// So, write the additionalMetadata into the response.Type string,
	// and the contents of the file into response.Message string.
	var response ApiResponse

	// both additional metadata and file are optional
	form, err := c.MultipartForm()

	metadata, ok := form.Value["additionalMetadata"]
	if ok {
		response.Type = metadata[0]
	}

	fileHdrs, ok := form.File["file"]
	if ok {
		fileHdr := fileHdrs[0]
		file, err := fileHdr.Open()
		if err == nil {
			bytes := make([]byte, fileHdr.Size)
			_, err := file.Read(bytes)
			if err == nil || err == io.EOF {
				response.Message = string(bytes)
			}
		}
	}

	response.Code = 0

	c.JSON(http.StatusOK, response)
}

