package api

import (
	"database/sql"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	db "git.nochill.in/nochill/hiling_go/db/sqlc"
	"git.nochill.in/nochill/hiling_go/util"
	"github.com/gin-gonic/gin"
	"github.com/lib/pq"
	ysqlc "github.com/yiplee/sqlc"
)

type createLocationReq struct {
	Address        string `form:"address" binding:"required"`
	Name           string `form:"name" binding:"required"`
	SubmittedBy    int32  `form:"submitted_by" binding:"required,number"`
	RegencyID      int16  `form:"regency_id" binding:"required,number"`
	GoogleMapsLink string `form:"google_maps_link"`
}

func (server *Server) createLocation(ctx *gin.Context) {
	var req createLocationReq
	var imgPath string

	var thumbnail, _ = ctx.FormFile("thumbnail")

	if err := ctx.Bind(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
		return
	}

	if thumbnail != nil {
		img := thumbnail
		fileExt := filepath.Ext(img.Filename)
		now := time.Now()
		dir := fmt.Sprintf("public/upload/images/locations/%s/thumbnail", req.Name)
		osFilename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
		imgPath = fmt.Sprintf("%s%s", dir, osFilename)

		if _, err := os.Stat(dir); os.IsNotExist(err) {
			os.Mkdir(dir, 0775)
		}

		if err := ctx.SaveUploadedFile(img, imgPath); err != nil {
			ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Error while try to save thumbnail image"))
			return
		}
	}

	arg := db.CreateLocationParams{
		Address:        req.Address,
		Name:           req.Name,
		SubmittedBy:    req.SubmittedBy,
		RegencyID:      req.RegencyID,
		GoogleMapsLink: sql.NullString{Valid: len(req.GoogleMapsLink) > 0, String: req.GoogleMapsLink},
	}

	err := server.Store.CreateLocation(ctx, arg)

	if err != nil {
		if pqErr, ok := err.(*pq.Error); ok {
			switch pqErr.Code.Name() {
			case "foreign_key_violation", "unique_violation":
				if pqErr.Constraint == "locations_regency_id_fkey" {
					ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Failed to submit location, there's no regency with id: %d", arg.RegencyID)))
					return
				}
				if pqErr.Constraint == "submitted_by_fkey" {
					ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Failed to submit location, there's no user with id: %d", arg.SubmittedBy)))
					return
				}
			}
		}
		ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
		return
	}

	ctx.Writer.WriteHeader(http.StatusOK)

}

type getListLocationsReq struct {
	Page     int32 `form:"page" binding:"required,min=1"`
	PageSize int32 `form:"page_size" binding:"required,min=5"`
}

func (server *Server) getListLocations(ctx *gin.Context) {
	var req getListLocationsReq
	if err := ctx.ShouldBindQuery(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
		return
	}

	locations, err := server.Store.GetListLocations(ysqlc.Build(ctx, func(builder *ysqlc.Builder) {
		builder.Limit(int(req.PageSize))
		builder.Offset(int(req.Page-1) * int(req.PageSize))
		builder.Order("created_at ASC")
	}))

	if err != nil {
		ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
		return
	}

	ctx.JSON(http.StatusOK, locations)
}

type getTopListLocationsReq struct {
	Page       int32 `form:"page" binding:"required,min=1"`
	PageSize   int32 `form:"page_size" binding:"required,min=5"`
	OrderBy    int16 `form:"order_by" binding:"numeric,min=1,max=3"`
	RegionType int16 `form:"region_type" binding:"numeric,min=0,max=7"`
}

func (server *Server) getTopListLocations(ctx *gin.Context) {
	var req getTopListLocationsReq
	var orderby string

	if err := ctx.ShouldBindQuery(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
		return
	}

	switch req.OrderBy {
	case 1:
		orderby = "iq2.avg_bayes"
	case 2:
		orderby = "iq2.critic_bayes"
	case 3:
		orderby = "iq2.user_bayes"
	}

	arg := db.GetTopListLocationsParams{
		Limit:      req.PageSize,
		Offset:     (req.Page - 1) * req.PageSize,
		OrderBy:    orderby,
		RegionType: sql.NullInt16{Valid: req.RegionType > 0, Int16: req.RegionType},
	}

	locations, err := server.Store.GetTopListLocations(ctx, arg)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
		return
	}

	ctx.JSON(http.StatusOK, locations)
}

type getListRecentLocationsWithRatingsReq struct {
	Page int32 `form:"page_size" binding:"required,min=1"`
}

func (server *Server) getListRecentLocationsWithRatings(ctx *gin.Context) {
	var req getListRecentLocationsWithRatingsReq
	if err := ctx.ShouldBindQuery(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
		return
	}

	locations, err := server.Store.GetListRecentLocationsWithRatings(ctx, req.Page)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
		return
	}

	ctx.JSON(http.StatusOK, locations)
}

type getLocationReq struct {
	ID int32 `uri:"location_id" binding:"required"`
}

func (server *Server) getLocation(ctx *gin.Context) {
	var req getLocationReq
	if err := ctx.ShouldBindUri(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
		return
	}

	location, err := server.Store.GetLocation(ctx, req.ID)
	if err != nil {
		if err == sql.ErrNoRows {
			ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
			return
		}
		ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while retrieving location detail"))
		return
	}

	tags, err := server.Store.GetLocationTag(ctx, req.ID)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, ErrorResponse(err, "Something went wrong while retrieving location tags"))
		return
	}

	res := gin.H{
		"tags":   tags,
		"detail": location,
	}

	ctx.JSON(http.StatusOK, res)
}