package repository

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/glebarez/sqlite"
	"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
	"gorm.io/gorm"
)

func TestSapronakIncomingPurchaseQueryPartsUsesAttributedPurchasesWhenProjectFlockKandangIDsProvided(t *testing.T) {
	sql, args := sapronakIncomingPurchaseQueryParts(SapronakQueryParams{
		WarehouseIDs:           []uint{46},
		ProjectFlockKandangIDs: []uint{101},
	})

	if sql != sapronakIncomingPurchasesScopedSQL() {
		t.Fatalf("expected scoped purchase SQL, got %q", sql)
	}
	if len(args) != 11 {
		t.Fatalf("expected 11 argument groups, got %d", len(args))
	}
}

func TestFetchSapronakIncomingIncludesAttributedFarmPurchasesAndHistoricalWarehouseFallback(t *testing.T) {
	db := setupClosingRepositoryTestDB(t)
	repo := NewClosingRepository(db)
	ctx := context.Background()

	receivedAt := time.Date(2026, 4, 1, 4, 0, 0, 0, time.UTC)
	statements := []string{
		`INSERT INTO warehouses (id, kandang_id) VALUES (1, NULL), (2, 59), (3, 88)`,
		`INSERT INTO product_categories (id, code) VALUES (1, 'OBT'), (2, 'RAW')`,
		`INSERT INTO products (id, name, product_category_id, product_price) VALUES
			(10, 'MEFISTO @1 LITER', 1, 261700),
			(20, 'PAKAN GROWING CRUMBLE MALINDO', 2, 15000)`,
		`INSERT INTO flags (id, flagable_id, flagable_type, name) VALUES
			(1, 10, 'products', 'OVK'),
			(2, 10, 'products', 'OBAT')`,
		`INSERT INTO purchases (id, po_number, deleted_at) VALUES (1, 'PO-LTI-0005', NULL)`,
		`INSERT INTO recordings (id, project_flock_kandangs_id, deleted_at) VALUES (11, 101, NULL), (12, 999, NULL)`,
		`INSERT INTO recording_stocks (id, recording_id, product_warehouse_id, usage_qty, project_flock_kandang_id) VALUES (21, 11, 501, 150, 101), (22, 12, 502, 10, 999)`,
		`INSERT INTO purchase_items (id, purchase_id, product_id, warehouse_id, project_flock_kandang_id, total_qty, price, received_date) VALUES
			(1, 1, 10, 1, NULL, 100, 261700, '` + receivedAt.Format(time.RFC3339) + `'),
			(2, 1, 20, 1, NULL, 50, 15000, '` + receivedAt.Format(time.RFC3339) + `'),
			(3, 1, 20, 2, NULL, 25, 12000, '` + receivedAt.Format(time.RFC3339) + `'),
			(4, 1, 10, 3, 999, 10, 261700, '` + receivedAt.Format(time.RFC3339) + `'),
			(5, 1, 20, 1, NULL, 40, 15000, '` + receivedAt.Format(time.RFC3339) + `')`,
		fmt.Sprintf(`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, allocation_purpose, status) VALUES
			(1, 701, '%s', 1, '%s', 21, 100, 'CONSUME', 'ACTIVE'),
			(2, 702, '%s', 2, '%s', 21, 50, 'CONSUME', 'ACTIVE'),
			(3, 703, '%s', 5, '%s', 22, 40, 'CONSUME', 'ACTIVE')`,
			fifo.StockableKeyPurchaseItems.String(),
			fifo.UsableKeyRecordingStock.String(),
			fifo.StockableKeyPurchaseItems.String(),
			fifo.UsableKeyRecordingStock.String(),
			fifo.StockableKeyPurchaseItems.String(),
			fifo.UsableKeyRecordingStock.String(),
		),
	}
	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed seeding schema: %v", err)
		}
	}

	rows, err := repo.FetchSapronakIncoming(ctx, 101, 59, nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(rows) != 2 {
		t.Fatalf("expected 2 sapronak rows, got %d", len(rows))
	}

	byProduct := make(map[uint]SapronakIncomingRow, len(rows))
	for _, row := range rows {
		byProduct[row.ProductID] = row
	}

	if got := byProduct[10]; got.ProductID == 0 || got.Flag != "OVK" || got.Qty != 100 {
		t.Fatalf("expected OVK farm purchase qty 100 for product 10, got %+v", got)
	}

	if got := byProduct[20]; got.ProductID == 0 || got.Flag != "PAKAN" || got.Qty != 75 {
		t.Fatalf("expected PAKAN total qty 75 including farm allocated qty 50 and kandang receipt qty 25, got %+v", got)
	}
}

func TestFetchSapronakAdjustmentsUsesAdjustmentReferenceAndPrice(t *testing.T) {
	db := setupClosingRepositoryTestDB(t)
	repo := NewClosingRepository(db)
	ctx := context.Background()

	statements := []string{
		`INSERT INTO warehouses (id, kandang_id) VALUES (5, 5)`,
		`INSERT INTO product_categories (id, code) VALUES (1, 'OBT')`,
		`INSERT INTO products (id, name, product_category_id, product_price) VALUES (17, 'OVK CUT-OVER', 1, 1)`,
		`INSERT INTO flags (id, flagable_id, flagable_type, name) VALUES (1, 17, 'products', 'OVK')`,
		`INSERT INTO product_warehouses (id, product_id, warehouse_id, project_flock_kandang_id) VALUES (1365, 17, 5, 66)`,
		`INSERT INTO adjustment_stocks (id, product_warehouse_id, total_qty, usage_qty, price) VALUES
			(1139, 1365, 1, 0, 298594487),
			(1140, 1365, 0, 1, 298594487)`,
		fmt.Sprintf(`INSERT INTO stock_allocations (id, product_warehouse_id, stockable_type, stockable_id, usable_type, usable_id, qty, allocation_purpose, status) VALUES
			(25990, 1365, '%s', 1139, '%s', 1140, 1, 'CONSUME', 'ACTIVE')`,
			fifo.StockableKeyAdjustmentIn.String(),
			fifo.UsableKeyAdjustmentOut.String(),
		),
	}
	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed seeding schema: %v", err)
		}
	}

	incoming, outgoing, err := repo.FetchSapronakAdjustments(ctx, 5, nil, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	incomingRows := incoming[17]
	if len(incomingRows) != 1 {
		t.Fatalf("expected 1 incoming row for product 17, got %d", len(incomingRows))
	}
	if incomingRows[0].Reference != "ADJ-1139" {
		t.Fatalf("expected incoming reference ADJ-1139, got %q", incomingRows[0].Reference)
	}
	if incomingRows[0].Price != 298594487 {
		t.Fatalf("expected incoming price 298594487 from adjustment_stocks.price, got %.3f", incomingRows[0].Price)
	}

	outgoingRows := outgoing[17]
	if len(outgoingRows) != 1 {
		t.Fatalf("expected 1 outgoing row for product 17, got %d", len(outgoingRows))
	}
	if outgoingRows[0].Reference != "ADJ-1139" {
		t.Fatalf("expected outgoing reference ADJ-1139, got %q", outgoingRows[0].Reference)
	}
	if outgoingRows[0].Reference == "CHICKIN-" {
		t.Fatalf("expected outgoing reference to avoid CHICKIN- placeholder")
	}
	if outgoingRows[0].Price != 298594487 {
		t.Fatalf("expected outgoing price 298594487 from adjustment_stocks.price, got %.3f", outgoingRows[0].Price)
	}
}

func setupClosingRepositoryTestDB(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 warehouses (
			id INTEGER PRIMARY KEY,
			kandang_id INTEGER NULL
		)`,
		`CREATE TABLE product_categories (
			id INTEGER PRIMARY KEY,
			code TEXT NOT NULL
		)`,
		`CREATE TABLE uoms (
			id INTEGER PRIMARY KEY,
			name TEXT NOT NULL
		)`,
		`CREATE TABLE products (
			id INTEGER PRIMARY KEY,
			name TEXT NOT NULL,
			product_category_id INTEGER NULL,
			uom_id INTEGER NULL,
			product_price NUMERIC(15,3) NOT NULL DEFAULT 0
		)`,
		`CREATE TABLE flags (
			id INTEGER PRIMARY KEY,
			flagable_id INTEGER NOT NULL,
			flagable_type TEXT NOT NULL,
			name TEXT NOT NULL
		)`,
		`CREATE TABLE purchases (
			id INTEGER PRIMARY KEY,
			po_number TEXT NULL,
			notes TEXT NULL,
			deleted_at TIMESTAMP NULL
		)`,
		`CREATE TABLE purchase_items (
			id INTEGER PRIMARY KEY,
			purchase_id INTEGER NOT NULL,
			product_id INTEGER NOT NULL,
			warehouse_id INTEGER NOT NULL,
			product_warehouse_id INTEGER NULL,
			project_flock_kandang_id INTEGER NULL,
			total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
			price NUMERIC(15,3) NOT NULL DEFAULT 0,
			received_date TIMESTAMP NULL
		)`,
		`CREATE TABLE recordings (
			id INTEGER PRIMARY KEY,
			project_flock_kandangs_id INTEGER NOT NULL,
			deleted_at TIMESTAMP NULL
		)`,
		`CREATE TABLE recording_stocks (
				id INTEGER PRIMARY KEY,
				recording_id INTEGER NOT NULL,
				product_warehouse_id INTEGER NOT NULL,
				usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
				project_flock_kandang_id INTEGER NULL
			)`,
		`CREATE TABLE project_chickins (
			id INTEGER PRIMARY KEY,
			project_flock_kandang_id INTEGER NOT NULL,
			chick_in_date TIMESTAMP NULL
		)`,
		`CREATE TABLE project_flock_populations (
			id INTEGER PRIMARY KEY,
			project_chickin_id INTEGER NULL,
			product_warehouse_id INTEGER NULL
		)`,
		`CREATE TABLE stock_allocations (
			id INTEGER PRIMARY KEY,
			product_warehouse_id INTEGER NOT NULL,
			stockable_type TEXT NOT NULL,
			stockable_id INTEGER NOT NULL,
			usable_type TEXT NOT NULL,
			usable_id INTEGER NOT NULL,
			qty NUMERIC(15,3) NOT NULL DEFAULT 0,
			allocation_purpose TEXT NOT NULL,
			status TEXT NOT NULL
		)`,
		`CREATE TABLE product_warehouses (
			id INTEGER PRIMARY KEY,
			product_id INTEGER NOT NULL,
			warehouse_id INTEGER NOT NULL,
			project_flock_kandang_id INTEGER NULL
		)`,
		`CREATE TABLE stock_transfers (
			id INTEGER PRIMARY KEY,
			from_warehouse_id INTEGER NULL,
			to_warehouse_id INTEGER NULL,
			transfer_date TIMESTAMP NULL,
			movement_number TEXT NULL,
			reason TEXT NULL
		)`,
		`CREATE TABLE laying_transfers (
			id INTEGER PRIMARY KEY,
			transfer_date TIMESTAMP NULL,
			transfer_number TEXT NULL
		)`,
		`CREATE TABLE laying_transfer_targets (
			id INTEGER PRIMARY KEY,
			laying_transfer_id INTEGER NOT NULL,
			product_warehouse_id INTEGER NULL
		)`,
		`CREATE TABLE stock_transfer_details (
			id INTEGER PRIMARY KEY,
			stock_transfer_id INTEGER NOT NULL,
			product_id INTEGER NOT NULL,
			dest_product_warehouse_id INTEGER NULL,
			source_product_warehouse_id INTEGER NULL,
			total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
			usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0
		)`,
		`CREATE TABLE adjustment_stocks (
			id INTEGER PRIMARY KEY,
			product_warehouse_id INTEGER NOT NULL,
			total_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
			usage_qty NUMERIC(15,3) NOT NULL DEFAULT 0,
			price NUMERIC(15,3) NOT NULL DEFAULT 0,
			adj_number TEXT NULL,
			created_at TIMESTAMP NULL
		)`,
	}

	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed preparing schema: %v", err)
		}
	}

	return db
}
