package test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	"gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gitlab.com/mbugroup/lti-api.git/internal/route"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
)

func setupIntegrationApp(t *testing.T) (*fiber.App, *gorm.DB) {
	t.Helper()
	dir := t.TempDir()
	dsn := filepath.Join(dir, "integration.db")

	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		t.Fatalf("failed to open sqlite database: %v", err)
	}

	if err := db.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
		t.Fatalf("failed to enable foreign keys: %v", err)
	}

	if err := db.AutoMigrate(
		&entities.User{},
		&entities.Area{},
		&entities.Location{},
		&entities.Flock{},
		&entities.ProjectFlock{},
		&entities.ProjectFlockKandang{},
		&entities.Kandang{},
		&entities.Warehouse{},
		&entities.Uom{},
		&entities.Customer{},
		&entities.Supplier{},
		&entities.Flag{},
		&entities.ProductCategory{},
		&entities.Nonstock{},
		&entities.NonstockSupplier{},
		&entities.Product{},
		&entities.ProductSupplier{},
		&entities.Fcr{},
		&entities.FcrStandard{},
		&entities.Bank{},
	); err != nil {
		t.Fatalf("auto migrate failed: %v", err)
	}

	seedUser := entities.User{
		Id:        1,
		IdUser:    1001,
		Email:     "tester@example.com",
		Name:      "Tester",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	if err := db.Create(&seedUser).Error; err != nil {
		t.Fatalf("failed to seed user: %v", err)
	}

	app := fiber.New(fiber.Config{ErrorHandler: utils.ErrorHandler})
	route.Routes(app, db)
	return app, db
}

func doJSONRequest(t *testing.T, app *fiber.App, method, path string, payload any) (*http.Response, []byte) {
	t.Helper()

	var body io.Reader
	if payload != nil {
		buf := &bytes.Buffer{}
		if err := json.NewEncoder(buf).Encode(payload); err != nil {
			t.Fatalf("failed to encode payload: %v", err)
		}
		body = buf
	}

	req := httptest.NewRequest(method, path, body)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	resp, err := app.Test(req, -1)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("failed to read response body: %v", err)
	}
	return resp, data
}

func parseID(t *testing.T, body []byte) uint {
	t.Helper()
	var resp struct {
		Data struct {
			Id uint `json:"id"`
		} `json:"data"`
	}
	if err := json.Unmarshal(body, &resp); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	return resp.Data.Id
}

func createArea(t *testing.T, app *fiber.App, name string) uint {
	t.Helper()
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": name})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating area, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func createLocation(t *testing.T, app *fiber.App, name, address string, areaID uint) uint {
	t.Helper()
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
		"name":    name,
		"address": address,
		"area_id": areaID,
	})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating location, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func createUom(t *testing.T, app *fiber.App, name string) uint {
	t.Helper()
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/uoms", map[string]any{"name": name})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating uom, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func createKandang(t *testing.T, app *fiber.App, name string, locationID, picID uint) uint {
	t.Helper()
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
		"name":        name,
		"status":      "ACTIVE",
		"location_id": locationID,
		"pic_id":      picID,
	})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating kandang, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func createCustomer(t *testing.T, app *fiber.App, name string, picID uint) uint {
	t.Helper()
	identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_"))
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
		"name":           name,
		"pic_id":         picID,
		"type":           utils.CustomerSupplierTypeBisnis,
		"address":        "Customer address",
		"phone":          "081234567890",
		"email":          fmt.Sprintf("%s@example.com", identifier),
		"account_number": fmt.Sprintf("ACC-%s", identifier),
	})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating customer, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func fetchCustomer(t *testing.T, db *gorm.DB, id uint) entities.Customer {
	t.Helper()
	var customer entities.Customer
	if err := db.Preload("Pic").Preload("CreatedUser").First(&customer, id).Error; err != nil {
		t.Fatalf("failed to fetch customer: %v", err)
	}
	return customer
}

func fetchKandang(t *testing.T, db *gorm.DB, id uint) entities.Kandang {
	t.Helper()
	var kandang entities.Kandang
	if err := db.Preload("ProjectFlock").First(&kandang, id).Error; err != nil {
		t.Fatalf("failed to fetch kandang: %v", err)
	}
	return kandang
}

func createSupplier(t *testing.T, app *fiber.App, name, alias, category string) uint {
	t.Helper()
	identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_"))
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/suppliers", map[string]any{
		"name":           name,
		"alias":          alias,
		"pic":            "John Doe",
		"type":           utils.CustomerSupplierTypeBisnis,
		"category":       category,
		"hatchery":       "Hatchery A",
		"phone":          "081234567890",
		"email":          fmt.Sprintf("%s@supplier.com", identifier),
		"address":        "Supplier address",
		"npwp":           "NPWP-123",
		"account_number": "ACC-SUPPLIER",
		"due_date":       30,
	})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating supplier, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func createProductCategory(t *testing.T, app *fiber.App, name, code string) uint {
	t.Helper()
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/product-categories", map[string]any{
		"name": name,
		"code": code,
	})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating product category, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func fetchProductCategory(t *testing.T, db *gorm.DB, id uint) entities.ProductCategory {
	t.Helper()
	var pc entities.ProductCategory
	if err := db.Preload("CreatedUser").First(&pc, id).Error; err != nil {
		t.Fatalf("failed to fetch product category: %v", err)
	}
	return pc
}

func createProduct(t *testing.T, app *fiber.App, name, brand string, sku *string, uomID, categoryID uint, productPrice float64, supplierIDs []uint, flags []string) uint {
	t.Helper()
	payload := map[string]any{
		"name":                name,
		"brand":               brand,
		"uom_id":              uomID,
		"product_category_id": categoryID,
		"product_price":       productPrice,
		"supplier_ids":        supplierIDs,
	}
	if sku != nil {
		payload["sku"] = *sku
	}
	if len(flags) > 0 {
		payload["flags"] = flags
	}
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", payload)
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating product, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func fetchProduct(t *testing.T, db *gorm.DB, id uint) entities.Product {
	t.Helper()
	var product entities.Product
	if err := db.Preload("CreatedUser").
		Preload("Uom").
		Preload("ProductCategory").
		Preload("Suppliers", func(tx *gorm.DB) *gorm.DB { return tx.Order("suppliers.name ASC") }).
		Preload("Flags", func(tx *gorm.DB) *gorm.DB { return tx.Order("flags.name ASC") }).
		First(&product, id).Error; err != nil {
		t.Fatalf("failed to fetch product: %v", err)
	}
	return product
}

func fetchSupplier(t *testing.T, db *gorm.DB, id uint) entities.Supplier {
	t.Helper()
	var supplier entities.Supplier
	if err := db.Preload("CreatedUser").First(&supplier, id).Error; err != nil {
		t.Fatalf("failed to fetch supplier: %v", err)
	}
	return supplier
}

func createFcr(t *testing.T, app *fiber.App, name string, standards []map[string]any) uint {
	t.Helper()
	payload := map[string]any{
		"name":          name,
		"fcr_standards": standards,
	}
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/fcrs", payload)
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating fcr, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func createFlock(t *testing.T, app *fiber.App, name string) uint {
	t.Helper()
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/flocks", map[string]any{
		"name": name,
	})
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating flock, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func fetchFcr(t *testing.T, db *gorm.DB, id uint) entities.Fcr {
	t.Helper()
	var fcr entities.Fcr
	if err := db.Preload("CreatedUser").
		Preload("Standards", func(tx *gorm.DB) *gorm.DB {
			return tx.Order("weight ASC")
		}).
		First(&fcr, id).Error; err != nil {
		t.Fatalf("failed to fetch fcr: %v", err)
	}
	return fcr
}

func createNonstock(t *testing.T, app *fiber.App, name string, uomID uint, supplierIDs []uint, flags []string) uint {
	t.Helper()
	payload := map[string]any{
		"name":         name,
		"uom_id":       uomID,
		"supplier_ids": supplierIDs,
	}
	if len(flags) > 0 {
		payload["flags"] = flags
	}
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", payload)
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating nonstock, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func fetchNonstock(t *testing.T, db *gorm.DB, id uint) entities.Nonstock {
	t.Helper()
	var nonstock entities.Nonstock
	if err := db.Preload("CreatedUser").
		Preload("Uom").
		Preload("Suppliers", func(tx *gorm.DB) *gorm.DB { return tx.Order("suppliers.name ASC") }).
		Preload("Flags", func(tx *gorm.DB) *gorm.DB { return tx.Order("flags.name ASC") }).
		First(&nonstock, id).Error; err != nil {
		t.Fatalf("failed to fetch nonstock: %v", err)
	}
	return nonstock
}

func fetchAreaName(t *testing.T, db *gorm.DB, id uint) string {
	t.Helper()
	var area entities.Area
	if err := db.First(&area, id).Error; err != nil {
		t.Fatalf("failed to fetch area: %v", err)
	}
	return area.Name
}

func fetchWarehouse(t *testing.T, db *gorm.DB, id uint) entities.Warehouse {
	t.Helper()
	var wh entities.Warehouse
	if err := db.First(&wh, id).Error; err != nil {
		t.Fatalf("failed to fetch warehouse: %v", err)
	}
	return wh
}

func createBank(t *testing.T, app *fiber.App, name, alias, accountNumber string, owner any) uint {
	t.Helper()
	payload := map[string]any{
		"name":           name,
		"alias":          alias,
		"account_number": accountNumber,
		"owner":          owner,
	}
	resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/banks", payload)
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected 201 when creating bank, got %d: %s", resp.StatusCode, string(body))
	}
	return parseID(t, body)
}

func fetchBank(t *testing.T, db *gorm.DB, id uint) entities.Bank {
	t.Helper()
	var bank entities.Bank
	if err := db.Preload("CreatedUser").First(&bank, id).Error; err != nil {
		t.Fatalf("failed to fetch bank: %v", err)
	}
	return bank
}
