package datatable

import (
	"cargo-erp-backend/internal/handlers/dto"
	"fmt"
	"strings"

	"gorm.io/gorm"
)

var allowedDirections = map[string]bool{
	"ASC":  true,
	"DESC": true,
}

type DatatableInterface interface {
	Render(coldef []dto.DataTableColDef) dto.DataTableResponse
}
type Datatable struct {
	DB  *gorm.DB
	Req dto.DataTableRequest
}

func NewDatatable(db *gorm.DB, table_request dto.DataTableRequest) DatatableInterface {
	return &Datatable{
		DB:  db,
		Req: table_request,
	}
}
func (d *Datatable) SetTable(db *gorm.DB) {
	d.DB = db
}
func (d *Datatable) Render(coldef []dto.DataTableColDef) dto.DataTableResponse {
	var totaldata int64 = 0
	d.DB.Count(&totaldata)
	//select
	selectAlias := make([]string, 0)
	for _, v := range coldef {
		selectAlias = append(selectAlias, fmt.Sprintf("%v AS %v", v.Field, v.Alias))
	}
	d.DB.Select(selectAlias)
	//global filter
	if len(d.Req.Search.Value) > 0 {
		g := d.DB.Session(&gorm.Session{})
		for _, vcol := range d.Req.Columns {
			if vcol.Searchable {
				for _, v := range coldef {
					if v.Alias == vcol.Data {
						g = g.Or(fmt.Sprintf("%v::TEXT ILIKE ?", v.Field), "%"+d.Req.Search.Value+"%")
					}
				}
			}
		}
		d.DB.Where(g)
	}
	//order
	if len(d.Req.Order) > 0 {
		for _, vOrder := range d.Req.Order {
			dir := strings.ToUpper(vOrder.Dir)
			if !allowedDirections[dir] {
				dir = "ASC"
			}
			for _, vcol := range d.Req.Columns {
				if vcol.Orderable && vOrder.Column == vcol.Data {
					for _, v := range coldef {
						if v.Alias == vcol.Data {
							d.DB.Order(fmt.Sprintf("%v %v", v.Field, dir))
						}
					}
				}
			}
		}
	}
	//additional filter
	if len(d.Req.AdditionalFilter) > 0 {
		for k, vFilter := range d.Req.AdditionalFilter {
			for _, v := range coldef {
				if v.Alias == k {
					d.DB.Where(fmt.Sprintf("%v=?", v.Field), vFilter)
				}
			}
		}
	}

	var totalFiltered int64 = 0
	d.DB.Count(&totalFiltered)
	start := d.Req.Start
	if start == 0 {
		start = 1
	}
	limit := d.Req.Length
	d.DB.Limit(limit).Offset(start*limit - limit)
	output := make([]map[string]interface{}, 0)
	d.DB.Scan(&output)
	for _, voutput := range output {
		for kv, vv := range voutput {
			for _, v := range coldef {
				if v.Alias == kv {
					if v.Formatter != nil {
						voutput[kv] = v.Formatter(voutput, vv)
					}
				}
			}
		}
	}
	result := dto.DataTableResponse{
		Draw:            1,
		RecordsTotal:    totaldata,
		RecordsFiltered: totalFiltered,
		Data:            output,
	}
	return result
}
