package dto

import (
	"sort"
	"strings"
	"time"
)

type SapronakDetailDTO struct {
	ProductID      uint       `json:"product_id"`
	ProductName    string     `json:"product_name"`
	Flag           string     `json:"flag"`
	Tanggal        *time.Time `json:"tanggal,omitempty"`
	NoReferensi    string     `json:"no_referensi,omitempty"`
	JenisTransaksi string     `json:"jenis_transaksi,omitempty"`
	QtyMasuk       float64    `json:"qty_masuk"`
	QtyKeluar      float64    `json:"qty_keluar"`
	Harga          float64    `json:"harga"`
	Nilai          float64    `json:"nilai"`
}

type SapronakGroupDTO struct {
	Flag        string              `json:"flag"`
	Items       []SapronakDetailDTO `json:"items"`
	TotalMasuk  float64             `json:"total_masuk"`
	TotalKeluar float64             `json:"total_keluar"`
	SaldoAkhir  float64             `json:"saldo_akhir"`
	TotalNilai  float64             `json:"total_nilai"`
}

type SapronakItemDTO struct {
	ProductID     uint    `json:"product_id"`
	ProductName   string  `json:"product_name"`
	Flag          string  `json:"flag"`
	IncomingQty   float64 `json:"incoming_qty"`
	IncomingValue float64 `json:"incoming_value"`
	UsageQty      float64 `json:"usage_qty"`
	UsageValue    float64 `json:"usage_value"`
	RemainingQty  float64 `json:"remaining_qty"`
	AveragePrice  float64 `json:"average_price"`
}

type SapronakReportDTO struct {
	ProjectFlockKandangID uint               `json:"project_flock_kandang_id"`
	ProjectFlockID        uint               `json:"project_flock_id"`
	ProjectName           string             `json:"project_name"`
	KandangID             uint               `json:"kandang_id"`
	KandangName           string             `json:"kandang_name"`
	Period                int                `json:"period"`
	Status                string             `json:"status"`
	StartDate             *time.Time         `json:"start_date,omitempty"`
	EndDate               *time.Time         `json:"end_date,omitempty"`
	TotalIncomingValue    float64            `json:"total_incoming_value"`
	TotalUsageValue       float64            `json:"total_usage_value"`
	Items                 []SapronakItemDTO  `json:"items"`
	Groups                []SapronakGroupDTO `json:"groups,omitempty"`
}

// Simplified view for project-level sapronak response
type SapronakCategoryRowDTO struct {
	ID              int     `json:"id"`
	Date            string  `json:"date"`
	ReferenceNumber string  `json:"reference_number"`
	QtyIn           float64 `json:"qty_in"`
	QtyOut          float64 `json:"qty_out"`
	QtyUsed         float64 `json:"qty_used"`
	Description     string  `json:"description"`
	ProductCategory string  `json:"product_category"`
	UnitPrice       float64 `json:"unit_price"`
	TotalAmount     float64 `json:"total_amount"`
	Notes           string  `json:"notes"`
}

type SapronakCategoryTotalDTO struct {
	Label        string  `json:"label"`
	QtyIn        float64 `json:"qty_in"`
	QtyOut       float64 `json:"qty_out"`
	QtyUsed      float64 `json:"qty_used"`
	AvgUnitPrice float64 `json:"avg_unit_price"`
	TotalAmount  float64 `json:"total_amount"`
}

type SapronakCategoryDTO struct {
	Rows  []SapronakCategoryRowDTO `json:"rows"`
	Total SapronakCategoryTotalDTO `json:"total"`
}

type SapronakProjectAggregatedDTO struct {
	Doc    *SapronakCategoryDTO `json:"doc,omitempty"`
	Ovk    *SapronakCategoryDTO `json:"ovk,omitempty"`
	Pakan  *SapronakCategoryDTO `json:"pakan,omitempty"`
	Pullet *SapronakCategoryDTO `json:"pullet,omitempty"`
}

type ClosingSapronakItemDTO struct {
	Id                   uint64 `json:"id"`
	Date                 string `json:"date"`
	ReferenceNumber      string `json:"reference_number"`
	TransactionType      string `json:"transaction_type"`
	ProductName          string `json:"product_name"`
	ProductCategory      string `json:"product_category"`
	ProductSubCategory   string `json:"product_sub_category"`
	SourceWarehouse      string `json:"source_warehouse"`
	DestinationWarehouse string `json:"destination_warehouse,omitempty"`
	// Destination          string    `json:"destination,omitempty"`
	Quantity          float64   `json:"quantity"`
	Unit              string    `json:"unit"`
	FormattedQuantity string    `json:"formatted_quantity"`
	Notes             string    `json:"notes"`
	SortDate          time.Time `json:"-"`
}

type ClosingSapronakDTO struct {
	IncomingSapronak []ClosingSapronakItemDTO `json:"incoming_sapronak"`
	OutgoingSapronak []ClosingSapronakItemDTO `json:"outgoing_sapronak"`
}

type ClosingSapronakSummaryItemDTO struct {
	Category string        `json:"category"`
	TotalQty int64         `json:"total_qty"`
	Uom      UomSummaryDTO `json:"uom"`
}

type UomSummaryDTO struct {
	ID   uint   `json:"id"`
	Name string `json:"name"`
}

// === Mapper Functions for Aggregated Sapronak Response ===

func ToSapronakProjectAggregatedFromReports(reports []SapronakReportDTO, flag string, productFlags map[uint][]string) SapronakProjectAggregatedDTO {
	result := SapronakProjectAggregatedDTO{}

	if len(reports) == 0 {
		return result
	}

	rep := reports[0]
	return ToSapronakProjectAggregatedFromReport(&rep, flag, productFlags)
}

func ToSapronakProjectAggregatedFromReport(report *SapronakReportDTO, flag string, productFlags map[uint][]string) SapronakProjectAggregatedDTO {
	result := SapronakProjectAggregatedDTO{}

	if report == nil {
		report = &SapronakReportDTO{}
	}

	normalizeFlag := func(raw string) string {
		normalized := strings.ToUpper(strings.TrimSpace(raw))
		if normalized == "PULLET" {
			return "DOC"
		}
		return normalized
	}
	normalizeCutOverToken := func(raw string) string {
		normalized := strings.ToUpper(strings.TrimSpace(raw))
		normalized = strings.ReplaceAll(normalized, "-", "")
		normalized = strings.ReplaceAll(normalized, " ", "")
		return normalized
	}
	containsCutOver := func(values ...string) bool {
		for _, value := range values {
			if strings.Contains(normalizeCutOverToken(value), "CUTOVER") {
				return true
			}
		}
		return false
	}
	filter := normalizeFlag(flag)

	byFlag := map[string]**SapronakCategoryDTO{}
	if filter == "" || filter == "DOC" {
		result.Doc = &SapronakCategoryDTO{Rows: make([]SapronakCategoryRowDTO, 0)}
		byFlag["DOC"] = &result.Doc
	}
	if filter == "" || filter == "OVK" {
		result.Ovk = &SapronakCategoryDTO{Rows: make([]SapronakCategoryRowDTO, 0)}
		byFlag["OVK"] = &result.Ovk
	}
	if filter == "" || filter == "PAKAN" {
		result.Pakan = &SapronakCategoryDTO{Rows: make([]SapronakCategoryRowDTO, 0)}
		byFlag["PAKAN"] = &result.Pakan
	}

	formatDate := func(t *time.Time) string {
		if t == nil {
			return ""
		}
		return t.Format("02-Jan-2006")
	}

	flagOrder := map[string]int{
		"DOC":    0,
		"PAKAN":  0,
		"OVK":    0,
		"PULLET": 0,
	}

	buildFlagList := func(productID uint, fallback string) string {
		rawFlags := productFlags[productID]
		if len(rawFlags) == 0 {
			if fallback == "" {
				return ""
			}
			return fallback
		}
		seen := make(map[string]struct{}, len(rawFlags))
		ordered := make([]string, 0, len(rawFlags))
		for _, f := range rawFlags {
			flagName := strings.ToUpper(strings.TrimSpace(f))
			if flagName == "" {
				continue
			}
			if _, ok := seen[flagName]; ok {
				continue
			}
			seen[flagName] = struct{}{}
			ordered = append(ordered, flagName)
		}
		sort.SliceStable(ordered, func(i, j int) bool {
			li := ordered[i]
			lj := ordered[j]
			ri, iok := flagOrder[li]
			rj, jok := flagOrder[lj]
			if iok != jok {
				if iok {
					return true
				}
				return false
			}
			if iok && jok && ri != rj {
				return ri < rj
			}
			return li < lj
		})
		return strings.Join(ordered, " ")
	}

	for _, group := range report.Groups {
		flagKey := normalizeFlag(group.Flag)
		ptr := byFlag[flagKey]
		if ptr == nil || *ptr == nil {
			continue
		}
		target := *ptr

		rowIndexByProduct := make(map[string]int)

		getOrCreateRow := func(productKey string, base SapronakCategoryRowDTO) *SapronakCategoryRowDTO {
			if idx, ok := rowIndexByProduct[productKey]; ok {
				return &target.Rows[idx]
			}
			target.Rows = append(target.Rows, base)
			idx := len(target.Rows) - 1
			rowIndexByProduct[productKey] = idx
			return &target.Rows[idx]
		}

		for idx, item := range group.Items {
			refKey := strings.TrimSpace(item.NoReferensi)
			productKey := strings.ToUpper(flagKey + "|" + item.ProductName + "|" + refKey)
			if refKey == "" {
				productKey = strings.ToUpper(flagKey + "|" + item.ProductName + "|" + formatDate(item.Tanggal))
			}
			baseRow := SapronakCategoryRowDTO{
				ID:              idx + 1,
				Date:            formatDate(item.Tanggal),
				ReferenceNumber: item.NoReferensi,
				Description:     item.ProductName,
				ProductCategory: buildFlagList(item.ProductID, flagKey),
				UnitPrice:       item.Harga,
				Notes:           "-",
			}
			isCutOver := containsCutOver(baseRow.ProductCategory, baseRow.Description, item.ProductName)

			row := getOrCreateRow(productKey, baseRow)

			switch strings.ToLower(item.JenisTransaksi) {
			case "pembelian", "adjustment masuk", "mutasi masuk":
				row.QtyIn += item.QtyMasuk
				if item.Tanggal != nil {
					row.Date = formatDate(item.Tanggal)
				}
				if row.UnitPrice == 0 {
					if item.QtyMasuk > 0 && item.Nilai > 0 {
						row.UnitPrice = item.Nilai / item.QtyMasuk
					} else if item.Harga > 0 {
						row.UnitPrice = item.Harga
					}
				}
				if strings.ToLower(item.JenisTransaksi) == "mutasi masuk" {
					ref := strings.ToUpper(strings.TrimSpace(item.NoReferensi))
					if strings.HasPrefix(ref, "TL-") {
						row.Notes = "TRANSFER LAYING"
					} else if strings.HasPrefix(ref, "ST-") {
						row.Notes = "TRANSFER STOCK"
					}
				}
			case "pemakaian":
				price := row.UnitPrice
				if price == 0 {
					price = item.Harga
				}
				row.QtyUsed += item.QtyKeluar
				row.TotalAmount += item.QtyKeluar * price
			case "adjustment keluar":
				price := row.UnitPrice
				if price == 0 {
					price = item.Harga
				}
				if row.UnitPrice == 0 {
					row.UnitPrice = price
				}
				if isCutOver {
					row.QtyUsed += item.QtyKeluar
					row.TotalAmount += item.QtyKeluar * price
					continue
				}
				row.QtyOut += item.QtyKeluar
			case "mutasi keluar", "penjualan":
				row.QtyOut += item.QtyKeluar
				if strings.ToLower(item.JenisTransaksi) == "mutasi keluar" {
					ref := strings.ToUpper(strings.TrimSpace(item.NoReferensi))
					if strings.HasPrefix(ref, "TL-") {
						row.Notes = "TRANSFER LAYING"
					} else if strings.HasPrefix(ref, "ST-") {
						row.Notes = "TRANSFER STOCK"
					}
				}
			default:
				row.QtyIn += item.QtyMasuk
				row.TotalAmount += item.Nilai
				if row.QtyIn > 0 {
					row.UnitPrice = row.TotalAmount / row.QtyIn
				}
			}
		}

		for i := range target.Rows {
			target.Rows[i].ID = i + 1
		}
	}

	// For chicken categories, keep qty_used aligned with qty_in - qty_out.
	// Sales are excluded; usage represents remaining after transfers.
	adjustChicken := func(cat *SapronakCategoryDTO) {
		if cat == nil {
			return
		}
		for i := range cat.Rows {
			row := &cat.Rows[i]
			remaining := row.QtyIn - row.QtyOut
			if remaining < 0 {
				remaining = 0
			}
			row.QtyUsed = remaining
			if row.UnitPrice > 0 {
				row.TotalAmount = row.QtyUsed * row.UnitPrice
			}
		}
	}
	adjustChicken(result.Doc)
	adjustChicken(result.Pullet)

	buildTotals := func(cat *SapronakCategoryDTO, label string) {
		if cat == nil {
			return
		}
		var qtyIn, qtyOut, qtyUsed, total float64
		for _, r := range cat.Rows {
			qtyIn += r.QtyIn
			qtyOut += r.QtyOut
			qtyUsed += r.QtyUsed
			total += r.TotalAmount
		}
		avg := 0.0
		if qtyUsed > 0 {
			avg = total / qtyUsed
		}
		cat.Total = SapronakCategoryTotalDTO{
			Label:        label,
			QtyIn:        qtyIn,
			QtyOut:       qtyOut,
			QtyUsed:      qtyUsed,
			AvgUnitPrice: avg,
			TotalAmount:  total,
		}
	}

	buildTotals(result.Doc, "TOTAL DOC")
	buildTotals(result.Ovk, "TOTAL OVK")
	buildTotals(result.Pakan, "TOTAL PAKAN")

	// For chicken categories, enforce total qty_used = qty_in - qty_out.
	adjustChickenTotal := func(cat *SapronakCategoryDTO) {
		if cat == nil {
			return
		}
		remaining := cat.Total.QtyIn - cat.Total.QtyOut
		if remaining < 0 {
			remaining = 0
		}
		cat.Total.QtyUsed = remaining
		if cat.Total.AvgUnitPrice > 0 {
			cat.Total.TotalAmount = cat.Total.AvgUnitPrice * remaining
		}
	}
	adjustChickenTotal(result.Doc)
	adjustChickenTotal(result.Pullet)
	return result
}
