package User

import (
	"errors"
	"net/http"
	"strconv"

	dto "astrology-api/dto/astrology"
	"astrology-api/helpers"
	"astrology-api/services"

	"github.com/gin-gonic/gin"
)

var errInvalidID = errors.New("invalid id")

///////////////////////////////////////////////////////////
// Kundali Matching Controller
///////////////////////////////////////////////////////////

type MatchingController struct {
	service services.MatchingService
}

func NewMatchingController(service services.MatchingService) *MatchingController {
	return &MatchingController{service: service}
}

///////////////////////////////////////////////////////////
// Live Lookups
///////////////////////////////////////////////////////////

// couple handles the endpoints that share the boy/girl request body.
func (c *MatchingController) couple(
	ctx *gin.Context,
	call func(uint, dto.MatchingRequest) (*dto.VedicDataResponse, error),
	message string,
) {

	var request dto.MatchingRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	userID := ctx.MustGet("user_id").(uint)

	response, err := call(userID, request)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(http.StatusOK, helpers.SuccessResponse(message, response))
}

func (c *MatchingController) Ashtakoot(ctx *gin.Context) {
	c.couple(ctx, c.service.Ashtakoot, "Ashtakoot matching fetched successfully.")
}

func (c *MatchingController) Dashakoot(ctx *gin.Context) {
	c.couple(ctx, c.service.Dashakoot, "Dashakoot matching fetched successfully.")
}

func (c *MatchingController) Aggregate(ctx *gin.Context) {
	c.couple(ctx, c.service.Aggregate, "Match report fetched successfully.")
}

func (c *MatchingController) Papasamaya(ctx *gin.Context) {
	c.couple(ctx, c.service.Papasamaya, "Papasamaya matching fetched successfully.")
}

func (c *MatchingController) RajjuVedha(ctx *gin.Context) {
	c.couple(ctx, c.service.RajjuVedha, "Rajju and Vedha details fetched successfully.")
}

///////////////////////////////////////////////////////////
// Nakshatra Match
///////////////////////////////////////////////////////////

func (c *MatchingController) Nakshatra(ctx *gin.Context) {

	var request dto.NakshatraMatchRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	response, err := c.service.Nakshatra(request)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Nakshatra matching fetched successfully.", response),
	)
}

///////////////////////////////////////////////////////////
// Western Match
///////////////////////////////////////////////////////////

func (c *MatchingController) Western(ctx *gin.Context) {

	var request dto.WesternMatchRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	response, err := c.service.Western(request)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Western matching fetched successfully.", response),
	)
}

///////////////////////////////////////////////////////////
// Save
///////////////////////////////////////////////////////////

func (c *MatchingController) Save(ctx *gin.Context) {

	var request dto.MatchingRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	userID := ctx.MustGet("user_id").(uint)

	response, err := c.service.Save(userID, request)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusCreated,
		helpers.SuccessResponse("Matching saved successfully.", response),
	)
}

///////////////////////////////////////////////////////////
// List
///////////////////////////////////////////////////////////

func (c *MatchingController) List(ctx *gin.Context) {

	var request dto.ListMatchingRequest

	// An empty body is a valid "first page" request.
	_ = ctx.ShouldBindJSON(&request)

	userID := ctx.MustGet("user_id").(uint)

	response, err := c.service.List(userID, request)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Matching list fetched successfully.", response),
	)
}

///////////////////////////////////////////////////////////
// Show
///////////////////////////////////////////////////////////

func (c *MatchingController) Show(ctx *gin.Context) {

	matchingID, err := parseIDParam(ctx, "id")

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	userID := ctx.MustGet("user_id").(uint)

	response, err := c.service.Show(userID, matchingID)

	if err != nil {

		ctx.JSON(http.StatusNotFound, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Matching fetched successfully.", response),
	)
}

///////////////////////////////////////////////////////////
// Delete
///////////////////////////////////////////////////////////

func (c *MatchingController) Delete(ctx *gin.Context) {

	matchingID, err := parseIDParam(ctx, "id")

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	userID := ctx.MustGet("user_id").(uint)

	if err := c.service.Delete(userID, matchingID); err != nil {

		ctx.JSON(http.StatusNotFound, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Matching deleted successfully.", nil),
	)
}

///////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////

func parseIDParam(ctx *gin.Context, name string) (uint, error) {

	value, err := strconv.ParseUint(ctx.Param(name), 10, 64)

	if err != nil || value == 0 {
		return 0, errInvalidID
	}

	return uint(value), nil
}
