package controllers

import (
	"net/http"
	"strconv"

	"catalogue.midpdsa.com/src/db/catalogue"
	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5/pgtype"
)

type SearchController struct {
	queries *catalogue.Queries
}

type brandSpecSearchItem struct {
	Type         string `json:"type"`
	Table        string `json:"table"`
	ID           int32  `json:"id"`
	PartNumber   any    `json:"partNumber"`
	Product      any    `json:"product"`
	Serie        any    `json:"serie"`
	Quantity     any    `json:"quantity"`
	Position     any    `json:"position"`
	Tier         any    `json:"tier"`
	ImageProduct any    `json:"imageProduct"`
	VehicleAttrs any    `json:"vehicleAttrs"`
	Years        any    `json:"years"`
	YearsRange   any    `json:"yearsRange"`
	VehicleMake  string `json:"vehicle_make"`
	VehicleModel any    `json:"vehicle_model"`
}

func NewSearchController(queries *catalogue.Queries) *SearchController {
	return &SearchController{queries: queries}
}

func (ctrl *SearchController) SearchAllBrandSpecs(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	grouped, err := ctrl.searchGroupedBrandSpecs(c, vehicleMake, model, year)
	if err != nil {
		searchError(c, err)
		return
	}

	searchResponse(c, "all", vehicleMake, model, year, flattenGroupedSearch(grouped))
}

func (ctrl *SearchController) GeneralSearch(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	grouped, err := ctrl.searchGroupedBrandSpecs(c, vehicleMake, model, year)
	if err != nil {
		searchError(c, err)
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"make":        vehicleMake,
		"model":       model,
		"year":        textValue(year),
		"total_count": grouped.totalCount(),
		"data": gin.H{
			"shock": groupedPayload(grouped.Shock),
			"brake": groupedPayload(grouped.Brake),
			"disk":  groupedPayload(grouped.Disk),
			"strut": groupedPayload(grouped.Strut),
			"leg":   groupedPayload(grouped.Leg),
		},
	})
}

func (ctrl *SearchController) SearchShockBrandSpecs(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	rows, err := ctrl.queries.SearchShockBrandSpecs(c.Request.Context(), catalogue.SearchShockBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		searchError(c, err)
		return
	}

	results := make([]brandSpecSearchItem, 0, len(rows))
	for _, row := range rows {
		results = append(results, shockSearchItem(row))
	}
	searchResponse(c, "shock", vehicleMake, model, year, results)
}

func (ctrl *SearchController) SearchBrakeBrandSpecs(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	rows, err := ctrl.queries.SearchBrakeBrandSpecs(c.Request.Context(), catalogue.SearchBrakeBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		searchError(c, err)
		return
	}

	results := make([]brandSpecSearchItem, 0, len(rows))
	for _, row := range rows {
		results = append(results, brakeSearchItem(row))
	}
	searchResponse(c, "brake", vehicleMake, model, year, results)
}

func (ctrl *SearchController) SearchDiskBrandSpecs(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	rows, err := ctrl.queries.SearchDiskBrandSpecs(c.Request.Context(), catalogue.SearchDiskBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		searchError(c, err)
		return
	}

	results := make([]brandSpecSearchItem, 0, len(rows))
	for _, row := range rows {
		results = append(results, diskSearchItem(row))
	}
	searchResponse(c, "disk", vehicleMake, model, year, results)
}

func (ctrl *SearchController) SearchStrutBrandSpecs(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	rows, err := ctrl.queries.SearchStrutBrandSpecs(c.Request.Context(), catalogue.SearchStrutBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		searchError(c, err)
		return
	}

	results := make([]brandSpecSearchItem, 0, len(rows))
	for _, row := range rows {
		results = append(results, strutSearchItem(row))
	}
	searchResponse(c, "strut", vehicleMake, model, year, results)
}

func (ctrl *SearchController) SearchLegBrandSpecs(c *gin.Context) {
	vehicleMake, model, year, ok := searchRequestParams(c)
	if !ok {
		return
	}

	rows, err := ctrl.queries.SearchLegBrandSpecs(c.Request.Context(), catalogue.SearchLegBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		searchError(c, err)
		return
	}

	results := make([]brandSpecSearchItem, 0, len(rows))
	for _, row := range rows {
		results = append(results, legSearchItem(row))
	}
	searchResponse(c, "leg", vehicleMake, model, year, results)
}

func searchRequestParams(c *gin.Context) (string, string, pgtype.Text, bool) {
	vehicleMake := c.Query("make")
	if vehicleMake == "" {
		vehicleMake = c.Query("brand")
	}
	model := c.Query("model")
	yearValue := c.Query("year")

	if vehicleMake == "" || model == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "make and model are required"})
		return "", "", pgtype.Text{}, false
	}

	year := pgtype.Text{}
	if yearValue != "" {
		if _, err := strconv.Atoi(yearValue); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "year must be numeric"})
			return "", "", pgtype.Text{}, false
		}
		year = pgtype.Text{String: yearValue, Valid: true}
	}

	return vehicleMake, model, year, true
}

func searchResponse(c *gin.Context, productType, make, model string, year pgtype.Text, data []brandSpecSearchItem) {
	if data == nil {
		data = []brandSpecSearchItem{}
	}

	c.JSON(http.StatusOK, gin.H{
		"type":  productType,
		"make":  make,
		"model": model,
		"year":  textValue(year),
		"count": len(data),
		"data":  data,
	})
}

type groupedBrandSpecSearch struct {
	Shock []brandSpecSearchItem
	Brake []brandSpecSearchItem
	Disk  []brandSpecSearchItem
	Strut []brandSpecSearchItem
	Leg   []brandSpecSearchItem
}

func (ctrl *SearchController) searchGroupedBrandSpecs(c *gin.Context, vehicleMake, model string, year pgtype.Text) (groupedBrandSpecSearch, error) {
	ctx := c.Request.Context()
	grouped := groupedBrandSpecSearch{
		Shock: []brandSpecSearchItem{},
		Brake: []brandSpecSearchItem{},
		Disk:  []brandSpecSearchItem{},
		Strut: []brandSpecSearchItem{},
		Leg:   []brandSpecSearchItem{},
	}

	shockRows, err := ctrl.queries.SearchShockBrandSpecs(ctx, catalogue.SearchShockBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		return grouped, err
	}
	for _, row := range shockRows {
		grouped.Shock = append(grouped.Shock, shockSearchItem(row))
	}

	brakeRows, err := ctrl.queries.SearchBrakeBrandSpecs(ctx, catalogue.SearchBrakeBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		return grouped, err
	}
	for _, row := range brakeRows {
		grouped.Brake = append(grouped.Brake, brakeSearchItem(row))
	}

	diskRows, err := ctrl.queries.SearchDiskBrandSpecs(ctx, catalogue.SearchDiskBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		return grouped, err
	}
	for _, row := range diskRows {
		grouped.Disk = append(grouped.Disk, diskSearchItem(row))
	}

	strutRows, err := ctrl.queries.SearchStrutBrandSpecs(ctx, catalogue.SearchStrutBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		return grouped, err
	}
	for _, row := range strutRows {
		grouped.Strut = append(grouped.Strut, strutSearchItem(row))
	}

	legRows, err := ctrl.queries.SearchLegBrandSpecs(ctx, catalogue.SearchLegBrandSpecsParams{Make: vehicleMake, Model: model, Year: year})
	if err != nil {
		return grouped, err
	}
	for _, row := range legRows {
		grouped.Leg = append(grouped.Leg, legSearchItem(row))
	}

	return grouped, nil
}

func (grouped groupedBrandSpecSearch) totalCount() int {
	return len(grouped.Shock) + len(grouped.Brake) + len(grouped.Disk) + len(grouped.Strut) + len(grouped.Leg)
}

func flattenGroupedSearch(grouped groupedBrandSpecSearch) []brandSpecSearchItem {
	results := make([]brandSpecSearchItem, 0, grouped.totalCount())
	results = append(results, grouped.Shock...)
	results = append(results, grouped.Brake...)
	results = append(results, grouped.Disk...)
	results = append(results, grouped.Strut...)
	results = append(results, grouped.Leg...)
	return results
}

func groupedPayload(items []brandSpecSearchItem) gin.H {
	if items == nil {
		items = []brandSpecSearchItem{}
	}
	return gin.H{"count": len(items), "items": items}
}

func searchError(c *gin.Context, err error) {
	c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}

func shockSearchItem(row catalogue.SearchShockBrandSpecsRow) brandSpecSearchItem {
	return brandSpecSearchItem{
		Type: "shock", Table: "shock_brand_specs", ID: row.ID,
		PartNumber: textValue(row.PartNumber), Product: textValue(row.Product), Serie: textValue(row.Serie), Quantity: row.Quantity,
		Position: textValue(row.Position), Tier: textValue(row.Tier), ImageProduct: textValue(row.ImageProduct), VehicleAttrs: textValue(row.VehicleAttrs),
		Years: textValue(row.Years), YearsRange: textValue(row.YearsRange), VehicleMake: row.VehicleMake, VehicleModel: textValue(row.VehicleModel),
	}
}

func brakeSearchItem(row catalogue.SearchBrakeBrandSpecsRow) brandSpecSearchItem {
	return brandSpecSearchItem{
		Type: "brake", Table: "brake_brand_specs", ID: row.ID,
		PartNumber: textValue(row.PartNumber), Product: textValue(row.Product), Serie: textValue(row.Serie), Quantity: int4Value(row.Quantity),
		Position: textValue(row.Position), Tier: textValue(row.Tier), ImageProduct: textValue(row.ImageProduct), VehicleAttrs: textValue(row.VehicleAttrs),
		Years: textValue(row.Years), YearsRange: textValue(row.YearsRange), VehicleMake: row.VehicleMake, VehicleModel: textValue(row.VehicleModel),
	}
}

func diskSearchItem(row catalogue.SearchDiskBrandSpecsRow) brandSpecSearchItem {
	return brandSpecSearchItem{
		Type: "disk", Table: "disk_brand_specs", ID: row.ID,
		PartNumber: textValue(row.PartNumber), Product: textValue(row.Product), Serie: textValue(row.Serie), Quantity: int4Value(row.Quantity),
		Position: textValue(row.Position), Tier: textValue(row.Tier), ImageProduct: textValue(row.ImageProduct), VehicleAttrs: textValue(row.VehicleAttrs),
		Years: textValue(row.Years), YearsRange: textValue(row.YearsRange), VehicleMake: row.VehicleMake, VehicleModel: textValue(row.VehicleModel),
	}
}

func strutSearchItem(row catalogue.SearchStrutBrandSpecsRow) brandSpecSearchItem {
	return brandSpecSearchItem{
		Type: "strut", Table: "strut_brand_specs", ID: row.ID,
		PartNumber: textValue(row.PartNumber), Product: textValue(row.Product), Serie: textValue(row.Serie), Quantity: row.Quantity,
		Position: textValue(row.Position), Tier: textValue(row.Tier), ImageProduct: textValue(row.ImageProduct), VehicleAttrs: textValue(row.VehicleAttrs),
		Years: textValue(row.Years), YearsRange: textValue(row.YearsRange), VehicleMake: row.VehicleMake, VehicleModel: textValue(row.VehicleModel),
	}
}

func legSearchItem(row catalogue.SearchLegBrandSpecsRow) brandSpecSearchItem {
	return brandSpecSearchItem{
		Type: "leg", Table: "leg_brand_specs", ID: row.ID,
		PartNumber: textValue(row.PartNumber), Product: textValue(row.Product), Serie: textValue(row.Serie), Quantity: row.Quantity,
		Position: textValue(row.Position), Tier: textValue(row.Tier), ImageProduct: textValue(row.ImageProduct), VehicleAttrs: textValue(row.VehicleAttrs),
		Years: textValue(row.Years), YearsRange: textValue(row.YearsRange), VehicleMake: row.VehicleMake, VehicleModel: textValue(row.VehicleModel),
	}
}

func textValue(value pgtype.Text) any {
	if !value.Valid {
		return nil
	}
	return value.String
}

func int4Value(value pgtype.Int4) any {
	if !value.Valid {
		return nil
	}
	return value.Int32
}
