package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm/clause"
	"log/slog"
	"net/http"
	"strconv"
)

// Pagination helper middleware
// This just checks and inserts some default pagination values
func paginator() gin.HandlerFunc {
	return func(c *gin.Context) {
		queryPage := c.DefaultQuery("page", "1")
		queryPageSize := c.DefaultQuery("page_size", "10")

		// add some defaults here if user gives us empty values
		if queryPage == "" {
			queryPage = "1"
		}
		if queryPageSize == "" {
			queryPageSize = "10"
		}

		page, err := strconv.Atoi(queryPage)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"Error": "Failed to convert page parameter to number"})
			c.Abort()
			return
		}

		pageSize, err := strconv.Atoi(queryPageSize)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"Error": "Failed to convert page_size parameter to number"})
			c.Abort()
			return
		}

		if page < 1 {
			page = 1
		}

		if pageSize < 10 {
			pageSize = 10
		}

		// Calculate the correct SQL offset for this page
		offset := (page - 1) * pageSize
		c.Set("page", page)
		c.Set("page_size", pageSize)
		c.Set("page_offset", offset)

		// Process request
		c.Next()
	}
}

func (env *Env) alive(c *gin.Context) {
	c.String(http.StatusOK, "yes!")
}

func (env *Env) getArtistByID(c *gin.Context) {
	artistID := c.Params.ByName("artistID")
	spotifyAuthToken := c.GetString("spotifyAuthToken")

	if artistID == "" || spotifyAuthToken == "Bearer " {
		c.JSON(http.StatusBadRequest, gin.H{"Error": "Could not find required parameters and/or required authentication tokens"})
		return
	}

	spotifyResponse, err := getSpotifyArtistData(artistID, spotifyAuthToken)
	if err != nil {
		// cast the error back into our custom type
		// so we can get context on why things failed
		statusCode := err.(*ResponseError).StatusCode
		switch statusCode {
		case 404:
			c.JSON(http.StatusNotFound, gin.H{"Error": "This artist does not exist"})
			return
		case 400:
			c.JSON(http.StatusBadRequest, gin.H{"Error": "Bad request"})
			return
		}
		slog.Error("[GOMUSIC] Failed to request latest spotify data from API", "Error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to request the latest data from spotify API"})
		return
	}

	// Update DB here
	var genreList []Genre
	for _, val := range spotifyResponse.Genres {
		genreList = append(genreList, Genre{Name: val})
	}

	artistProfile := &ArtistProfile{
		SpotifyID:  spotifyResponse.ID,
		Name:       spotifyResponse.Name,
		Popularity: spotifyResponse.Popularity,
		Genres:     genreList,
	}

	dbResult := env.db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "spotify_id"}},
		UpdateAll: true,
	}).Create(&artistProfile)

	if dbResult.Error != nil {
		slog.Error("[GOMUSIC] Failed to store response in local database", "Error", err)
	}

	c.JSON(http.StatusOK, artistProfile)
}

func (env *Env) getArtistByName(c *gin.Context) {
	artistName, exists := c.GetQuery("name")
	if !exists {
		c.JSON(http.StatusBadRequest, gin.H{"Error": "name parameter was not supplied"})
		return
	}
	pageSize := c.GetInt("page_size")
	pageOffset := c.GetInt("page_offset")

	// Lookup this name in the DB and return any ArtistProfile objects
	// NOTE: This is case insensitive for ascii on sqlite
	// NOTE: However unicode is treated case sensitive due to very difficult conversions for some languages/characters
	// NOTE: In future better DBs with unicode support such as postgres should be used
	var artistProfiles []ArtistProfile
	searchString := fmt.Sprintf("%%%s%%"), artistName)
	dbResult := env.db.Where("name LIKE ?", searchString).Preload("Genres").Offset(pageOffset).Limit(pageSize).Find(&artistProfiles)
	if dbResult.Error != nil {
		slog.Error("[GOMUSIC] Failed to query local database for artist name", "Name", artistName)
		c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to lookup name"})
		return
	}

	c.JSON(http.StatusOK, artistProfiles)
}

func (env *Env) getGenres(c *gin.Context) {
	var genres []Genre
	pageSize := c.GetInt("page_size")
	pageOffset := c.GetInt("page_offset")

	dbResult := env.db.Offset(pageOffset).Limit(pageSize).Find(&genres)
	if dbResult.Error != nil {
		slog.Error("[GOMUSIC] Failed to query local database for genre data")
		c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to query database for genre data"})
		return
	}
	recordNum := dbResult.RowsAffected

	// Construct simple slice for response
	genreList := make([]string, 0, recordNum)
	if recordNum > 0 {
		for _, g := range genres {
			genreList = append(genreList, g.Name)
		}
	}
	c.JSON(http.StatusOK, genreList)
}

func (env *Env) createGenre(c *gin.Context) {
	var userGenre Genre
	if c.ShouldBind(&userGenre) == nil {
		// If we didn't get the correct data abort early
		if userGenre.Name == "" {
			c.JSON(http.StatusBadRequest, gin.H{"Error": "Could not find genre name"})
			return
		}
		dbResult := env.db.Clauses(clause.OnConflict{
			DoNothing: true,
		}).Create(&userGenre)
		if dbResult.Error != nil {
			slog.Error("[GOMUSIC] Failed to create new genre record in DB")
			c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to create new genre record"})
			return
		}
		c.String(http.StatusOK, "Success")
		return
	}
	c.JSON(http.StatusInternalServerError, gin.H{"Error": "Failed to parse input data"})
}