package test

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"testing"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"

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

func TestCustomerIntegration(t *testing.T) {
	app, db := setupIntegrationApp(t)

	t.Run("creating customer without existing pic fails", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
			"name":           "Invalid Customer",
			"pic_id":         9999,
			"type":           utils.CustomerSupplierTypeBisnis,
			"address":        "Somewhere",
			"phone":          "0800000000",
			"email":          "Invalid@example.com",
			"account_number": "ACC-INVALID",
		})
		if resp.StatusCode != fiber.StatusNotFound {
			t.Fatalf("expected 404 when pic is missing, got %d: %s", resp.StatusCode, string(body))
		}
	})

	t.Run("creating customer with Invalid type fails", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
			"name":           "Invalid Type",
			"pic_id":         1,
			"type":           "UNKNOWN",
			"address":        "Somewhere",
			"phone":          "081234567891",
			"email":          "Invalid-type@example.com",
			"account_number": "ACC-INVALID-TYPE",
		})
		if resp.StatusCode != fiber.StatusBadRequest {
			t.Fatalf("expected 400 when type is Invalid, got %d: %s", resp.StatusCode, string(body))
		}
	})

	const customerName = "Customer Alpha"
	var customerID uint

	t.Run("creating customer succeeds", func(t *testing.T) {
		customerID = createCustomer(t, app, customerName, 1)
		customer := fetchCustomer(t, db, customerID)
		if customer.Name != customerName {
			t.Fatalf("expected name %q, got %q", customerName, customer.Name)
		}
		if customer.PicId != 1 {
			t.Fatalf("expected pic id 1, got %d", customer.PicId)
		}
		if customer.Type != string(utils.CustomerSupplierTypeBisnis) {
			t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeBisnis, customer.Type)
		}
	})

	t.Run("creating customer with duplicate name fails", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
			"name":           customerName,
			"pic_id":         1,
			"type":           utils.CustomerSupplierTypeBisnis,
			"address":        "Duplicate address",
			"phone":          "0811111111",
			"email":          "duplicate@example.com",
			"account_number": "ACC-DUP",
		})
		if resp.StatusCode != fiber.StatusConflict {
			t.Fatalf("expected 409 when creating duplicate customer, got %d: %s", resp.StatusCode, string(body))
		}
	})

	t.Run("getting existing customer succeeds", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
		if resp.StatusCode != fiber.StatusOK {
			t.Fatalf("expected 200 when fetching customer, got %d: %s", resp.StatusCode, string(body))
		}
		var payload struct {
			Data struct {
				Id   uint   `json:"id"`
				Name string `json:"name"`
			} `json:"data"`
		}
		if err := json.Unmarshal(body, &payload); err != nil {
			t.Fatalf("failed to parse customer response: %v", err)
		}
		if payload.Data.Id != customerID {
			t.Fatalf("expected id %d, got %d", customerID, payload.Data.Id)
		}
		if payload.Data.Name != customerName {
			t.Fatalf("expected name %q, got %q", customerName, payload.Data.Name)
		}
	})

	const updatedName = "Customer Gamma"

	t.Run("updating customer name succeeds", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
			"name": updatedName,
		})
		if resp.StatusCode != fiber.StatusOK {
			t.Fatalf("expected 200 when updating customer, got %d: %s", resp.StatusCode, string(body))
		}
		var payload struct {
			Data struct {
				Name string `json:"name"`
			} `json:"data"`
		}
		if err := json.Unmarshal(body, &payload); err != nil {
			t.Fatalf("failed to parse update response: %v", err)
		}
		if payload.Data.Name != updatedName {
			t.Fatalf("expected updated name %q, got %q", updatedName, payload.Data.Name)
		}
		customer := fetchCustomer(t, db, customerID)
		if customer.Name != updatedName {
			t.Fatalf("expected persisted name %q, got %q", updatedName, customer.Name)
		}
	})

	t.Run("updating customer type succeeds", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
			"type": utils.CustomerSupplierTypeIndividual,
		})
		if resp.StatusCode != fiber.StatusOK {
			t.Fatalf("expected 200 when updating customer type, got %d: %s", resp.StatusCode, string(body))
		}
		customer := fetchCustomer(t, db, customerID)
		if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
			t.Fatalf("expected persisted type %q, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
		}
	})

	t.Run("updating customer with Invalid type fails", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
			"type": "random-type",
		})
		if resp.StatusCode != fiber.StatusBadRequest {
			t.Fatalf("expected 400 when updating with Invalid type, got %d: %s", resp.StatusCode, string(body))
		}
		customer := fetchCustomer(t, db, customerID)
		if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
			t.Fatalf("expected type to remain %q after Invalid update, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
		}
	})

	t.Run("updating non existent customer returns 404", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodPatch, "/api/master-data/customers/9999", map[string]any{
			"name": "Does Not Matter",
		})
		if resp.StatusCode != fiber.StatusNotFound {
			t.Fatalf("expected 404 when updating missing customer, got %d: %s", resp.StatusCode, string(body))
		}
	})

	t.Run("deleting customer succeeds", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
		if resp.StatusCode != fiber.StatusOK {
			t.Fatalf("expected 200 when deleting customer, got %d: %s", resp.StatusCode, string(body))
		}
		var customer entities.Customer
		if err := db.First(&customer, customerID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
			t.Fatalf("expected customer to be deleted, got error %v", err)
		}
	})

	t.Run("deleting non existent customer returns 404", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
		if resp.StatusCode != fiber.StatusNotFound {
			t.Fatalf("expected 404 when deleting missing customer, got %d: %s", resp.StatusCode, string(body))
		}
	})

	t.Run("getting deleted customer returns 404", func(t *testing.T) {
		resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
		if resp.StatusCode != fiber.StatusNotFound {
			t.Fatalf("expected 404 when fetching deleted customer, got %d: %s", resp.StatusCode, string(body))
		}
	})
}
