package repository

import (
	"testing"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

func TestMarketingDeliveryAttributionRowsQueryIncludesMappedAndFallbackRows(t *testing.T) {
	db := setupMarketingAttributionTestDB(t)

	statements := []string{
		`INSERT INTO project_flocks (id, category) VALUES (1, 'LAYING')`,
		`INSERT INTO project_flock_kandangs (id, project_flock_id) VALUES (101, 1), (102, 1)`,
		`INSERT INTO project_chickins (id, project_flock_kandang_id) VALUES (201, 101), (202, 102)`,
		`INSERT INTO project_flock_populations (id, project_chickin_id) VALUES (301, 201), (302, 202)`,
		`INSERT INTO product_warehouses (id, project_flock_kandang_id) VALUES (401, NULL), (402, 101)`,
		`INSERT INTO marketing_products (id, product_warehouse_id) VALUES (501, 401), (502, 402), (503, 401)`,
		`INSERT INTO marketing_delivery_products (id, marketing_product_id, usage_qty) VALUES (601, 501, 100), (602, 502, 25), (603, 503, 12)`,
		`INSERT INTO recording_eggs (id, recording_id, project_flock_kandang_id) VALUES (701, NULL, 101)`,
		`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, status, allocation_purpose) VALUES
			(1, 401, 'PROJECT_FLOCK_POPULATION', 301, 'MARKETING_DELIVERY', 601, 60, 'ACTIVE', 'CONSUME'),
			(2, 401, 'PROJECT_FLOCK_POPULATION', 302, 'MARKETING_DELIVERY', 601, 40, 'ACTIVE', 'CONSUME'),
			(3, 401, 'RECORDING_EGG', 701, 'MARKETING_DELIVERY', 603, 12, 'ACTIVE', 'CONSUME')`,
	}
	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed seeding fixtures: %v", err)
		}
	}

	var rows []MarketingDeliveryAttributionRow
	if err := db.Table("(?) AS mda", MarketingDeliveryAttributionRowsQuery(db)).
		Order("mda.marketing_delivery_product_id ASC, mda.project_flock_kandang_id ASC").
		Scan(&rows).Error; err != nil {
		t.Fatalf("failed scanning attribution rows: %v", err)
	}

	if len(rows) != 4 {
		t.Fatalf("expected 4 attribution rows, got %d", len(rows))
	}
	if rows[0].MarketingDeliveryProductID != 601 || rows[0].ProjectFlockKandangID != 101 || rows[0].AllocatedQty != 60 {
		t.Fatalf("unexpected first attribution row: %+v", rows[0])
	}
	if rows[1].MarketingDeliveryProductID != 601 || rows[1].ProjectFlockKandangID != 102 || rows[1].AllocatedQty != 40 {
		t.Fatalf("unexpected second attribution row: %+v", rows[1])
	}
	if rows[2].MarketingDeliveryProductID != 602 || rows[2].ProjectFlockKandangID != 101 || rows[2].AllocatedQty != 25 {
		t.Fatalf("unexpected fallback attribution row: %+v", rows[2])
	}
	if rows[3].MarketingDeliveryProductID != 603 || rows[3].ProjectFlockKandangID != 101 || rows[3].AllocatedQty != 12 {
		t.Fatalf("unexpected egg attribution row: %+v", rows[3])
	}
}

func TestMarketingDeliverySingleAttributionQueryOnlyReturnsSingleSourceRows(t *testing.T) {
	db := setupMarketingAttributionTestDB(t)

	statements := []string{
		`INSERT INTO project_flocks (id, category) VALUES (1, 'LAYING')`,
		`INSERT INTO project_flock_kandangs (id, project_flock_id) VALUES (101, 1), (102, 1)`,
		`INSERT INTO project_chickins (id, project_flock_kandang_id) VALUES (201, 101), (202, 102)`,
		`INSERT INTO project_flock_populations (id, project_chickin_id) VALUES (301, 201), (302, 202)`,
		`INSERT INTO product_warehouses (id, project_flock_kandang_id) VALUES (401, NULL), (402, 101)`,
		`INSERT INTO marketing_products (id, product_warehouse_id) VALUES (501, 401), (502, 402), (503, 401)`,
		`INSERT INTO marketing_delivery_products (id, marketing_product_id, usage_qty) VALUES (601, 501, 100), (602, 502, 25), (603, 503, 12)`,
		`INSERT INTO recording_eggs (id, recording_id, project_flock_kandang_id) VALUES (701, NULL, 101)`,
		`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, status, allocation_purpose) VALUES
			(1, 401, 'PROJECT_FLOCK_POPULATION', 301, 'MARKETING_DELIVERY', 601, 60, 'ACTIVE', 'CONSUME'),
			(2, 401, 'PROJECT_FLOCK_POPULATION', 302, 'MARKETING_DELIVERY', 601, 40, 'ACTIVE', 'CONSUME'),
			(3, 401, 'RECORDING_EGG', 701, 'MARKETING_DELIVERY', 603, 12, 'ACTIVE', 'CONSUME')`,
	}
	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed seeding fixtures: %v", err)
		}
	}

	type singleRow struct {
		MarketingDeliveryProductID      uint  `gorm:"column:marketing_delivery_product_id"`
		AttributedProjectFlockKandangID *uint `gorm:"column:attributed_project_flock_kandang_id"`
	}

	var rows []singleRow
	if err := db.Table("(?) AS mda", MarketingDeliverySingleAttributionQuery(db)).
		Order("mda.marketing_delivery_product_id ASC").
		Scan(&rows).Error; err != nil {
		t.Fatalf("failed scanning single attribution rows: %v", err)
	}

	if len(rows) != 3 {
		t.Fatalf("expected 3 rows, got %d", len(rows))
	}
	if rows[0].MarketingDeliveryProductID != 601 || rows[0].AttributedProjectFlockKandangID != nil {
		t.Fatalf("expected pooled delivery 601 to have nil single attribution, got %+v", rows[0])
	}
	if rows[1].MarketingDeliveryProductID != 602 || rows[1].AttributedProjectFlockKandangID == nil || *rows[1].AttributedProjectFlockKandangID != 101 {
		t.Fatalf("expected fallback delivery 602 to map to kandang 101, got %+v", rows[1])
	}
	if rows[2].MarketingDeliveryProductID != 603 || rows[2].AttributedProjectFlockKandangID == nil || *rows[2].AttributedProjectFlockKandangID != 101 {
		t.Fatalf("expected egg delivery 603 to map to kandang 101, got %+v", rows[2])
	}
}

func setupMarketingAttributionTestDB(t *testing.T) *gorm.DB {
	t.Helper()

	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=private"), &gorm.Config{})
	if err != nil {
		t.Fatalf("failed opening sqlite db: %v", err)
	}

	statements := []string{
		`CREATE TABLE stock_allocations (
			id INTEGER PRIMARY KEY,
			product_warehouse_id INTEGER,
			stockable_type TEXT,
			stockable_id INTEGER,
			usable_type TEXT,
			usable_id INTEGER,
			qty NUMERIC(15,3),
			status TEXT,
			allocation_purpose TEXT
		)`,
		`CREATE TABLE project_flock_populations (id INTEGER PRIMARY KEY, project_chickin_id INTEGER)`,
		`CREATE TABLE project_chickins (id INTEGER PRIMARY KEY, project_flock_kandang_id INTEGER)`,
		`CREATE TABLE project_flock_kandangs (id INTEGER PRIMARY KEY, project_flock_id INTEGER)`,
		`CREATE TABLE project_flocks (id INTEGER PRIMARY KEY, category TEXT)`,
		`CREATE TABLE marketing_delivery_products (id INTEGER PRIMARY KEY, marketing_product_id INTEGER, usage_qty NUMERIC(15,3))`,
		`CREATE TABLE marketing_products (id INTEGER PRIMARY KEY, product_warehouse_id INTEGER)`,
		`CREATE TABLE product_warehouses (id INTEGER PRIMARY KEY, project_flock_kandang_id INTEGER NULL)`,
		`CREATE TABLE recording_eggs (id INTEGER PRIMARY KEY, recording_id INTEGER, project_flock_kandang_id INTEGER NULL)`,
		`CREATE TABLE recordings (id INTEGER PRIMARY KEY, project_flock_kandangs_id INTEGER NULL)`,
		`CREATE TABLE recording_depletions (id INTEGER PRIMARY KEY, recording_id INTEGER, source_project_flock_kandang_id INTEGER NULL)`,
		`CREATE TABLE purchase_items (id INTEGER PRIMARY KEY, project_flock_kandang_id INTEGER NULL)`,
		`CREATE TABLE stock_transfer_details (id INTEGER PRIMARY KEY, source_product_warehouse_id INTEGER NULL)`,
		`CREATE TABLE laying_transfer_targets (id INTEGER PRIMARY KEY, target_project_flock_kandang_id INTEGER NULL)`,
	}
	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed preparing schema: %v", err)
		}
	}

	return db
}
