package repositories

import (
	"context"
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
	"gorm.io/gorm"
)

type HppPerKandangRow struct {
	ProjectFlockKandangID uint
	ProjectFlockPeriod    int
	KandangID             uint
	KandangName           string
	KandangStatus         string
	LocationID            uint
	LocationName          string
	PicID                 uint
	PicName               string
	RecordingCount        int64
	// RemainingChickenBirds          float64
	// RemainingChickenWeight         float64
	EggProductionWeightKgRemaining float64
	// AverageWeightEggPerPiece       float64
	// EggProductionPiecesRemaining   float64
	// EggProductionTotalWeightKg     float64
	// EggProductionTotalPieces       float64
}

type HppPerKandangCostRow struct {
	ProjectFlockKandangID uint
	FeedCost              float64
	OvkCost               float64
	DocCost               float64
	DocQty                float64
	BudgetCost            float64
	ExpenseCost           float64
}

type HppPerKandangSupplierRow struct {
	ProjectFlockKandangID uint
	SupplierID            uint
	SupplierName          string
	SupplierAlias         string
	Category              string
}

type HppPerKandangRepository interface {
	GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error)
	GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error)
	GetWeightRemainingByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error)
}

type hppPerKandangRepository struct {
	db *gorm.DB
}

func NewHppPerKandangRepository(db *gorm.DB) HppPerKandangRepository {
	return &hppPerKandangRepository{db: db}
}

func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, end time.Time, areaIDs, locationIDs, kandangIDs []int64) ([]HppPerKandangRow, error) {
	var rows []HppPerKandangRow

	latestApproval := r.db.WithContext(ctx).
		Table("approvals AS a").
		Select("a.approvable_id, a.action").
		Joins(`
			JOIN (
				SELECT approvable_id, MAX(action_at) AS latest_action_at
				FROM approvals
				WHERE approvable_type = ?
				GROUP BY approvable_id
			) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
			string(utils.ApprovalWorkflowRecording),
		)

	validRecordings := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select("r.id, r.project_flock_kandangs_id, r.total_chick_qty").
		Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL").
		Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected))

	query := r.db.WithContext(ctx).
		Table("project_flocks AS pf").
		Select(`
			pfk.id AS project_flock_kandang_id,
			pfk.period AS project_flock_period,
			k.id AS kandang_id,
			k.name AS kandang_name,
			k.status AS kandang_status,
			loc.id AS location_id,
			loc.name AS location_name,
			pic.id AS pic_id,
			pic.name AS pic_name,
			COALESCE(COUNT(vr.id), 0) AS recording_count,
			COALESCE(MAX(vr.total_chick_qty), 0) AS remaining_chicken_birds,
			0 AS remaining_chicken_weight,
			0 AS egg_production_weight_kg,
			0 AS egg_production_pieces,
			0 AS egg_production_total_weight_kg,
			0 AS egg_production_total_pieces`).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
		Joins(`
			LEFT JOIN (
				SELECT project_flock_kandang_id, MIN(chick_in_date) AS chick_in_date
				FROM project_chickins
				GROUP BY project_flock_kandang_id
			) AS pc ON pc.project_flock_kandang_id = pfk.id`).
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN locations AS loc ON loc.id = k.location_id").
		Joins("JOIN users AS pic ON pic.id = k.pic_id").
		Joins("LEFT JOIN (?) AS vr ON vr.project_flock_kandangs_id = pfk.id", validRecordings).
		Where("pf.category = ?", utils.ProjectFlockCategoryLaying).
		Where("(pfk.closed_at IS NULL OR ? BETWEEN pc.chick_in_date AND pfk.closed_at)", start)

	query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)

	query = query.Group("pfk.id, pfk.period, k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
		Order("pfk.id ASC")

	if err := query.Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) ([]HppPerKandangCostRow, []HppPerKandangSupplierRow, error) {
	var rows []HppPerKandangCostRow

	docRows := make([]struct {
		ProjectFlockKandangID uint
		DocCost               float64
		DocQty                float64
		SupplierID            *uint
		SupplierName          *string
		SupplierAlias         *string
	}, 0)

	docQuery := r.db.WithContext(ctx).
		Table("project_chickins AS pc").
		Select(`
			pfk.id AS project_flock_kandang_id,
			COALESCE(SUM(sa.qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
			COALESCE(SUM(sa.qty), 0) AS doc_qty,
			s.id AS supplier_id,
			s.name AS supplier_name,
			s.alias AS supplier_alias`).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN locations AS loc ON loc.id = k.location_id").
		Joins("LEFT JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.stockable_type = ? AND sa.status = ? AND sa.allocation_purpose = ?", fifo.UsableKeyProjectChickin.String(), fifo.StockableKeyPurchaseItems.String(), entity.StockAllocationStatusActive, entity.StockAllocationPurposeTraceChickin).
		Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id").
		Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
		Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
		Where("pc.project_flock_kandang_id IN ?", projectFlockKandangIDs).
		Group("pfk.id, s.id, s.name, s.alias")

	if err := docQuery.Scan(&docRows).Error; err != nil {
		return nil, nil, err
	}

	costMap := make(map[uint]*HppPerKandangCostRow, len(rows))
	for i := range rows {
		row := rows[i]
		costMap[row.ProjectFlockKandangID] = &rows[i]
	}

	docSuppliers := make([]HppPerKandangSupplierRow, 0)
	docSeen := make(map[uint]map[uint]bool)
	for _, doc := range docRows {
		entry, ok := costMap[doc.ProjectFlockKandangID]
		if !ok {
			rows = append(rows, HppPerKandangCostRow{
				ProjectFlockKandangID: doc.ProjectFlockKandangID,
			})
			entry = &rows[len(rows)-1]
			costMap[doc.ProjectFlockKandangID] = entry
		}
		entry.DocCost += doc.DocCost
		entry.DocQty += doc.DocQty
		if doc.SupplierID != nil {
			if docSeen[doc.ProjectFlockKandangID] == nil {
				docSeen[doc.ProjectFlockKandangID] = make(map[uint]bool)
			}
			if !docSeen[doc.ProjectFlockKandangID][*doc.SupplierID] {
				docSeen[doc.ProjectFlockKandangID][*doc.SupplierID] = true
				supplierName := ""
				if doc.SupplierName != nil {
					supplierName = *doc.SupplierName
				}
				supplierAlias := ""
				if doc.SupplierAlias != nil {
					supplierAlias = *doc.SupplierAlias
				}
				docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{
					ProjectFlockKandangID: doc.ProjectFlockKandangID,
					SupplierID:            *doc.SupplierID,
					SupplierName:          supplierName,
					SupplierAlias:         supplierAlias,
					Category:              "DOC",
				})
			}
		}
	}

	feedRows := make([]struct {
		ProjectFlockKandangID uint
		FeedCost              float64
		SupplierID            *uint
		SupplierName          *string
		SupplierAlias         *string
	}, 0)

	feedQuery := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select(`
			rs.project_flock_kandang_id AS project_flock_kandang_id,
			COALESCE(SUM(sa.qty * COALESCE(pi.price, 0)), 0) AS feed_cost,
			s.id AS supplier_id,
			s.name AS supplier_name,
			s.alias AS supplier_alias`).
		Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id").
		Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id").
		Joins("JOIN flags AS f ON f.flagable_id = pw.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
		Joins("JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND sa.stockable_type = ? AND sa.status = ? AND sa.allocation_purpose = ?", fifo.UsableKeyRecordingStock.String(), fifo.StockableKeyPurchaseItems.String(), entity.StockAllocationStatusActive, entity.StockAllocationPurposeConsume).
		Joins("JOIN purchase_items AS pi ON pi.id = sa.stockable_id").
		Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
		Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
		Where("rs.project_flock_kandang_id IN ?", projectFlockKandangIDs).
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("f.name = ?", utils.FlagPakan).
		Group("rs.project_flock_kandang_id, s.id, s.name, s.alias")

	if err := feedQuery.Scan(&feedRows).Error; err != nil {
		return nil, nil, err
	}

	feedSuppliers := make([]HppPerKandangSupplierRow, 0)
	feedSeen := make(map[uint]map[uint]bool)
	for _, feed := range feedRows {
		entry, ok := costMap[feed.ProjectFlockKandangID]
		if !ok {
			rows = append(rows, HppPerKandangCostRow{
				ProjectFlockKandangID: feed.ProjectFlockKandangID,
			})
			entry = &rows[len(rows)-1]
			costMap[feed.ProjectFlockKandangID] = entry
		}
		entry.FeedCost += feed.FeedCost
		if feed.SupplierID != nil {
			if feedSeen[feed.ProjectFlockKandangID] == nil {
				feedSeen[feed.ProjectFlockKandangID] = make(map[uint]bool)
			}
			if !feedSeen[feed.ProjectFlockKandangID][*feed.SupplierID] {
				feedSeen[feed.ProjectFlockKandangID][*feed.SupplierID] = true
				supplierName := ""
				if feed.SupplierName != nil {
					supplierName = *feed.SupplierName
				}
				supplierAlias := ""
				if feed.SupplierAlias != nil {
					supplierAlias = *feed.SupplierAlias
				}
				feedSuppliers = append(feedSuppliers, HppPerKandangSupplierRow{
					ProjectFlockKandangID: feed.ProjectFlockKandangID,
					SupplierID:            *feed.SupplierID,
					SupplierName:          supplierName,
					SupplierAlias:         supplierAlias,
					Category:              "FEED",
				})
			}
		}
	}

	docSuppliers = append(docSuppliers, feedSuppliers...)
	return rows, docSuppliers, nil
}

func (r *hppPerKandangRepository) GetWeightRemainingByProjectFlockKandangIDs(ctx context.Context, start, end time.Time, projectFlockKandangIDs []uint) (map[uint]HppPerKandangRow, error) {
	if len(projectFlockKandangIDs) == 0 {
		return map[uint]HppPerKandangRow{}, nil
	}

	latestApproval := r.db.WithContext(ctx).
		Table("approvals AS a").
		Select("a.approvable_id, a.action").
		Joins(`
			JOIN (
				SELECT approvable_id, MAX(action_at) AS latest_action_at
				FROM approvals
				WHERE approvable_type = ?
				GROUP BY approvable_id
			) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
			string(utils.ApprovalWorkflowRecording),
		)

	type eggRow struct {
		ProjectFlockKandangID    uint
		AverageWeightEggPerPiece float64
		// EggProductionPiecesRemaining   float64
		// EggProductionTotalWeightKg     float64
		// EggProductionTotalPieces       float64
	}

	eggRows := make([]eggRow, 0)
	query := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select(`
			r.project_flock_kandangs_id AS project_flock_kandang_id,
			COALESCE(SUM(re.weight) / NULLIF(SUM(re.total_qty), 0), 0) AS average_weight_egg_per_piece`).
		Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
		Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id").
		Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
		Where("r.record_datetime < ?", end).
		Where("r.deleted_at IS NULL").
		Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
		Group("r.project_flock_kandangs_id")

	if err := query.Scan(&eggRows).Error; err != nil {
		return nil, err
	}

	result := make(map[uint]HppPerKandangRow, len(eggRows))
	for _, row := range eggRows {
		result[row.ProjectFlockKandangID] = HppPerKandangRow{
			ProjectFlockKandangID: row.ProjectFlockKandangID,
			// AverageWeightEggPerPiece: row.AverageWeightEggPerPiece,
			// EggProductionPiecesRemaining:   row.EggProductionPiecesRemaining,
			// EggProductionTotalWeightKg:     row.EggProductionTotalWeightKg,
			// EggProductionTotalPieces:       row.EggProductionTotalPieces,
		}
	}

	return result, nil
}

func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int64) *gorm.DB {
	if len(areaIDs) > 0 {
		query = query.Where("loc.area_id IN ?", areaIDs)
	}
	if len(locationIDs) > 0 {
		query = query.Where("k.location_id IN ?", locationIDs)
	}
	if len(kandangIDs) > 0 {
		query = query.Where("pfk.id IN ?", kandangIDs)
	}
	return query
}
