package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"

	"wucher/internal/domain/health_insurance_companies"
	"wucher/internal/service"
	"wucher/internal/shared/pkg/uuidv7"
)

type memHealthInsuranceCompaniesRepo struct {
	rows map[string]*health_insurance_companies.HealthInsuranceCompany

	createErr  error
	updateErr  error
	deleteErr  error
	getByIDErr error
	listErr    error

	listRows  []health_insurance_companies.HealthInsuranceCompany
	listTotal int64

	getByIDFn func(id []byte, call int) (*health_insurance_companies.HealthInsuranceCompany, error)
	getCalls  int

	lastCreate     *health_insurance_companies.HealthInsuranceCompany
	lastUpdate     *health_insurance_companies.HealthInsuranceCompany
	lastDeleteID   []byte
	lastDeleteBy   []byte
	lastListFilter string
	lastListSort   string
	lastListLimit  int
	lastListOffset int
}

func newMemHealthInsuranceCompaniesRepo() *memHealthInsuranceCompaniesRepo {
	return &memHealthInsuranceCompaniesRepo{
		rows: map[string]*health_insurance_companies.HealthInsuranceCompany{},
	}
}

func (r *memHealthInsuranceCompaniesRepo) Create(_ context.Context, row *health_insurance_companies.HealthInsuranceCompany) error {
	if r.createErr != nil {
		return r.createErr
	}
	if len(row.ID) == 0 {
		row.ID = uuidv7.MustBytes()
	}
	r.rows[string(row.ID)] = row
	r.lastCreate = row
	return nil
}

func (r *memHealthInsuranceCompaniesRepo) Update(_ context.Context, row *health_insurance_companies.HealthInsuranceCompany) error {
	if r.updateErr != nil {
		return r.updateErr
	}
	r.rows[string(row.ID)] = row
	r.lastUpdate = row
	return nil
}

func (r *memHealthInsuranceCompaniesRepo) Delete(_ context.Context, id []byte, deletedBy []byte) error {
	if r.deleteErr != nil {
		return r.deleteErr
	}
	r.lastDeleteID = append([]byte(nil), id...)
	r.lastDeleteBy = append([]byte(nil), deletedBy...)
	delete(r.rows, string(id))
	return nil
}

func (r *memHealthInsuranceCompaniesRepo) GetByID(_ context.Context, id []byte) (*health_insurance_companies.HealthInsuranceCompany, error) {
	r.getCalls++
	if r.getByIDFn != nil {
		return r.getByIDFn(id, r.getCalls)
	}
	if r.getByIDErr != nil {
		return nil, r.getByIDErr
	}
	return r.rows[string(id)], nil
}

func (r *memHealthInsuranceCompaniesRepo) List(_ context.Context, filter, sort string, limit, offset int) ([]health_insurance_companies.HealthInsuranceCompany, int64, error) {
	r.lastListFilter = filter
	r.lastListSort = sort
	r.lastListLimit = limit
	r.lastListOffset = offset
	if r.listErr != nil {
		return nil, 0, r.listErr
	}
	return r.listRows, r.listTotal, nil
}

func (r *memHealthInsuranceCompaniesRepo) ResolveStateDetails(_ context.Context, stateNames []string) (map[string]health_insurance_companies.StateDetail, error) {
	out := map[string]health_insurance_companies.StateDetail{}
	for i := range stateNames {
		if stateNames[i] == "Berlin" {
			out["berlin"] = health_insurance_companies.StateDetail{
				FederalStateID: "019dfb03-afc1-7ad0-8606-715fe5fdc57f",
				FederalState:   "Berlin",
				LandName:       "Deutschland",
				LandISOCode:    "DE",
			}
		}
	}
	return out, nil
}

func (r *memHealthInsuranceCompaniesRepo) ResolveLandDetails(_ context.Context, landIDs []string) (map[string]health_insurance_companies.LandDetail, error) {
	out := map[string]health_insurance_companies.LandDetail{}
	for i := range landIDs {
		key := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(landIDs[i]), "-", ""))
		if key == "018f3a5c7a6f7c2a8d4a4b5b1f6c8e9d" {
			out[key] = health_insurance_companies.LandDetail{
				LandID:      key,
				LandName:    "Deutschland",
				LandISOCode: "DE",
			}
		}
	}
	return out, nil
}

func newHealthInsuranceCompaniesTestApp(h *HealthInsuranceCompaniesHandler, actor []byte) *fiber.App {
	app := fiber.New()
	app.Use(func(c *fiber.Ctx) error {
		if len(actor) > 0 {
			c.Locals("user_id", actor)
		}
		return c.Next()
	})
	app.Post("/api/v1/health-insurance-companies/create", h.Create)
	app.Patch("/api/v1/health-insurance-companies/update/:uuid", h.Update)
	app.Delete("/api/v1/health-insurance-companies/delete/:uuid", h.Delete)
	app.Get("/api/v1/health-insurance-companies/get-all", h.List)
	app.Get("/api/v1/health-insurance-companies/get-all/dt", h.ListDatatable)
	return app
}

func doJSONRequestHealthInsuranceCompanies(t *testing.T, app *fiber.App, method, path string, body []byte) (*http.Response, []byte) {
	t.Helper()
	req, err := http.NewRequest(method, path, bytes.NewReader(body))
	if err != nil {
		t.Fatalf("new request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("read body: %v", err)
	}
	return resp, respBody
}

func mustUUIDStringHealthInsuranceCompanies(t *testing.T, id []byte) string {
	t.Helper()
	s, err := uuidv7.BytesToString(id)
	if err != nil {
		t.Fatalf("uuid to string: %v", err)
	}
	return s
}

func TestNewHealthInsuranceCompaniesHandler(t *testing.T) {
	h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(newMemHealthInsuranceCompaniesRepo()))
	if h == nil || h.validate == nil {
		t.Fatalf("expected handler and validator initialized")
	}
}

func TestHealthInsuranceCompaniesHandlerCreate(t *testing.T) {
	t.Run("invalid json", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", []byte(`{"data":`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("validation error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", []byte(`{}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("name empty", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		body := []byte(`{"data":{"type":"health_insurance_company","attributes":{"name":"  "}}}`)
		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", body)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("create error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.createErr = errors.New("create failed")
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		body := []byte(`{"data":{"type":"health_insurance_company","attributes":{"name":"Main"}}}`)
		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", body)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("success with refetch", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		actor := uuidv7.MustBytes()
		app := newHealthInsuranceCompaniesTestApp(h, actor)

		body := []byte(`{"data":{"type":"health_insurance_company","attributes":{"name":"Main","sortkey":3,"state":"Berlin","address":"Main Street","mobile_number":"+491234","email":"main@hic.local","is_active":false}}}`)
		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d", resp.StatusCode)
		}
		if repo.lastCreate == nil {
			t.Fatalf("expected row created")
		}
		if repo.lastCreate.State != "Berlin" || repo.lastCreate.Address != "Main Street" || repo.lastCreate.MobileNumber != "+491234" || repo.lastCreate.Email != "main@hic.local" {
			t.Fatalf("expected optional attributes to be mapped")
		}
		if repo.lastCreate.SortKey == nil || *repo.lastCreate.SortKey != 3 {
			t.Fatalf("expected sortkey mapped")
		}
		if repo.lastCreate.IsActive {
			t.Fatalf("expected is_active false")
		}
		if string(repo.lastCreate.CreatedBy) != string(actor) || string(repo.lastCreate.UpdatedBy) != string(actor) {
			t.Fatalf("expected created_by and updated_by from actor")
		}
	})

	t.Run("success with land_id fallback", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		body := []byte(`{"data":{"type":"health_insurance_company","attributes":{"name":"Main","land_id":"018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d"}}}`)
		resp, body := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d", resp.StatusCode)
		}

		var doc struct {
			Data struct {
				Attributes struct {
					LandID   string `json:"land_id"`
					LandName string `json:"land_name"`
				} `json:"attributes"`
			} `json:"data"`
		}
		if err := json.Unmarshal(body, &doc); err != nil {
			t.Fatalf("unmarshal body: %v", err)
		}
		if doc.Data.Attributes.LandID != "018f3a5c-7a6f-7c2a-8d4a-4b5b1f6c8e9d" {
			t.Fatalf("expected land_id returned")
		}
		if doc.Data.Attributes.LandName != "Deutschland" {
			t.Fatalf("expected land_name Deutschland, got %q", doc.Data.Attributes.LandName)
		}
	})
	t.Run("success fallback when refetch error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.getByIDFn = func(_ []byte, call int) (*health_insurance_companies.HealthInsuranceCompany, error) {
			if call > 0 {
				return nil, errors.New("lookup failed")
			}
			return nil, nil
		}
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		body := []byte(`{"data":{"type":"health_insurance_company","attributes":{"name":"Main"}}}`)
		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPost, "/api/v1/health-insurance-companies/create", body)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d", resp.StatusCode)
		}
	})
}

func TestHealthInsuranceCompaniesHandlerUpdate(t *testing.T) {
	t.Run("invalid uuid", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/not-uuid", []byte(`{}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("invalid json", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+id, []byte(`{"data":`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("validation error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+id, []byte(`{"data":{"type":"x","id":"`+id+`","attributes":{"name":"A"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("id required", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+id, []byte(`{"data":{"type":"health_insurance_company","id":"","attributes":{"name":"A"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("id mismatch", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())
		other := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+id, []byte(`{"data":{"type":"health_insurance_company","id":"`+other+`","attributes":{"name":"A"}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("no attributes", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+id, []byte(`{"data":{"type":"health_insurance_company","id":"`+id+`","attributes":{}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("not found", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+id, []byte(`{"data":{"type":"health_insurance_company","id":"`+id+`","attributes":{"name":"A"}}}`))
		if resp.StatusCode != http.StatusNotFound {
			t.Fatalf("expected 404, got %d", resp.StatusCode)
		}
	})

	t.Run("name empty", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		id := uuidv7.MustBytes()
		repo.rows[string(id)] = &health_insurance_companies.HealthInsuranceCompany{ID: id, Name: "Old"}
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		idStr := mustUUIDStringHealthInsuranceCompanies(t, id)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+idStr, []byte(`{"data":{"type":"health_insurance_company","id":"`+idStr+`","attributes":{"name":"  "}}}`))
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("update error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		id := uuidv7.MustBytes()
		repo.rows[string(id)] = &health_insurance_companies.HealthInsuranceCompany{ID: id, Name: "Old"}
		repo.updateErr = errors.New("update failed")
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		idStr := mustUUIDStringHealthInsuranceCompanies(t, id)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+idStr, []byte(`{"data":{"type":"health_insurance_company","id":"`+idStr+`","attributes":{"name":"New"}}}`))
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("success with refetch", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		id := uuidv7.MustBytes()
		repo.rows[string(id)] = &health_insurance_companies.HealthInsuranceCompany{ID: id, Name: "Old"}
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		actor := uuidv7.MustBytes()
		app := newHealthInsuranceCompaniesTestApp(h, actor)
		idStr := mustUUIDStringHealthInsuranceCompanies(t, id)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+idStr, []byte(`{"data":{"type":"health_insurance_company","id":"`+idStr+`","attributes":{"name":"New","state":"Bayern","address":"Street 1","mobile_number":"+49888","email":"new@hic.local"}}}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if repo.lastUpdate == nil || repo.lastUpdate.Name != "New" || repo.lastUpdate.State != "Bayern" || repo.lastUpdate.Address != "Street 1" || repo.lastUpdate.MobileNumber != "+49888" || repo.lastUpdate.Email != "new@hic.local" {
			t.Fatalf("expected updated values")
		}
		if string(repo.lastUpdate.UpdatedBy) != string(actor) {
			t.Fatalf("expected updated_by from actor")
		}
	})

	t.Run("success update sortkey and is_active", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		id := uuidv7.MustBytes()
		repo.rows[string(id)] = &health_insurance_companies.HealthInsuranceCompany{ID: id, Name: "Old", IsActive: true}
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		idStr := mustUUIDStringHealthInsuranceCompanies(t, id)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+idStr, []byte(`{"data":{"type":"health_insurance_company","id":"`+idStr+`","attributes":{"sortkey":2,"is_active":false}}}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if repo.lastUpdate == nil {
			t.Fatalf("expected update called")
		}
		if repo.lastUpdate.SortKey == nil || *repo.lastUpdate.SortKey != 2 {
			t.Fatalf("expected sortkey updated")
		}
		if repo.lastUpdate.IsActive {
			t.Fatalf("expected is_active false")
		}
	})

	t.Run("success fallback when refetch fails", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		id := uuidv7.MustBytes()
		existing := &health_insurance_companies.HealthInsuranceCompany{ID: id, Name: "Old"}
		repo.getByIDFn = func(_ []byte, call int) (*health_insurance_companies.HealthInsuranceCompany, error) {
			if call == 1 {
				return existing, nil
			}
			return nil, errors.New("second lookup failed")
		}
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		idStr := mustUUIDStringHealthInsuranceCompanies(t, id)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodPatch, "/api/v1/health-insurance-companies/update/"+idStr, []byte(`{"data":{"type":"health_insurance_company","id":"`+idStr+`","attributes":{"state":"Only state"}}}`))
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
	})
}

func TestHealthInsuranceCompaniesHandlerDelete(t *testing.T) {
	t.Run("invalid uuid", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodDelete, "/api/v1/health-insurance-companies/delete/not-uuid", nil)
		if resp.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", resp.StatusCode)
		}
	})

	t.Run("delete error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.deleteErr = errors.New("delete failed")
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)
		id := mustUUIDStringHealthInsuranceCompanies(t, uuidv7.MustBytes())

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodDelete, "/api/v1/health-insurance-companies/delete/"+id, nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("success", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		actor := uuidv7.MustBytes()
		app := newHealthInsuranceCompaniesTestApp(h, actor)
		id := uuidv7.MustBytes()
		idStr := mustUUIDStringHealthInsuranceCompanies(t, id)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodDelete, "/api/v1/health-insurance-companies/delete/"+idStr, nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if string(repo.lastDeleteBy) != string(actor) {
			t.Fatalf("expected deleted_by from actor")
		}
	})
}

func TestHealthInsuranceCompaniesHandlerList(t *testing.T) {
	t.Run("list error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.listErr = errors.New("list failed")
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodGet, "/api/v1/health-insurance-companies/get-all", nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("success with filter name and clamp max", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		expectedID := uuidv7.MustBytes()
		expectedIDStr := mustUUIDStringHealthInsuranceCompanies(t, expectedID)
		repo.listRows = []health_insurance_companies.HealthInsuranceCompany{{ID: expectedID, Name: "A", State: "Berlin", SortKey: intPtrHICHandler(3), IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now()}}
		repo.listTotal = 1
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, body := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodGet, "/api/v1/health-insurance-companies/get-all?filter[name]=abc&page[number]=0&page=0&page[size]=0&limit=101&sort=-sortkey", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if repo.lastListFilter != "abc" || repo.lastListSort != "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END DESC, name ASC" || repo.lastListLimit != 0 || repo.lastListOffset != 0 {
			t.Fatalf("unexpected list params: filter=%q sort=%q limit=%d offset=%d", repo.lastListFilter, repo.lastListSort, repo.lastListLimit, repo.lastListOffset)
		}
		var doc struct {
			Data []struct {
				Type       string `json:"type"`
				ID         string `json:"id"`
				Attributes struct {
					LandName string `json:"land_name"`
				} `json:"attributes"`
			} `json:"data"`
		}
		if err := json.Unmarshal(body, &doc); err != nil {
			t.Fatalf("unmarshal body: %v", err)
		}
		if len(doc.Data) != 1 {
			t.Fatalf("expected 1 row, got %d", len(doc.Data))
		}
		if doc.Data[0].Type != "health_insurance_company" {
			t.Fatalf("unexpected type: %s", doc.Data[0].Type)
		}
		if doc.Data[0].ID != expectedIDStr {
			t.Fatalf("expected id %s, got %s", expectedIDStr, doc.Data[0].ID)
		}
		if doc.Data[0].Attributes.LandName != "Deutschland" {
			t.Fatalf("expected land_name Deutschland, got %q", doc.Data[0].Attributes.LandName)
		}
	})

	t.Run("success with search and clamp min", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.listRows = []health_insurance_companies.HealthInsuranceCompany{}
		repo.listTotal = 0
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodGet, "/api/v1/health-insurance-companies/get-all?page=2&size=-1&search=needle", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if repo.lastListFilter != "needle" || repo.lastListLimit != 0 || repo.lastListOffset != 0 {
			t.Fatalf("unexpected list params: filter=%q limit=%d offset=%d", repo.lastListFilter, repo.lastListLimit, repo.lastListOffset)
		}
	})
}

func TestHealthInsuranceCompaniesHandlerListDatatable(t *testing.T) {
	t.Run("list error", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.listErr = errors.New("list failed")
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodGet, "/api/v1/health-insurance-companies/get-all/dt", nil)
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected 400, got %d", resp.StatusCode)
		}
	})

	t.Run("success clamp max and page floor", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.listRows = []health_insurance_companies.HealthInsuranceCompany{{ID: uuidv7.MustBytes(), Name: "A", CreatedAt: time.Now(), UpdatedAt: time.Now()}}
		repo.listTotal = 1
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodGet, "/api/v1/health-insurance-companies/get-all/dt?page=0&limit=0&size=101&draw=7&search=abc", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if repo.lastListFilter != "abc" || repo.lastListLimit != 100 || repo.lastListOffset != 0 {
			t.Fatalf("unexpected list params: filter=%q limit=%d offset=%d", repo.lastListFilter, repo.lastListLimit, repo.lastListOffset)
		}
	})

	t.Run("success clamp min", func(t *testing.T) {
		repo := newMemHealthInsuranceCompaniesRepo()
		repo.listRows = []health_insurance_companies.HealthInsuranceCompany{}
		repo.listTotal = 0
		h := NewHealthInsuranceCompaniesHandler(service.NewHealthInsuranceCompaniesService(repo))
		app := newHealthInsuranceCompaniesTestApp(h, nil)

		resp, _ := doJSONRequestHealthInsuranceCompanies(t, app, http.MethodGet, "/api/v1/health-insurance-companies/get-all/dt?page=2&limit=0&size=-1", nil)
		if resp.StatusCode != http.StatusOK {
			t.Fatalf("expected 200, got %d", resp.StatusCode)
		}
		if repo.lastListLimit != 20 || repo.lastListOffset != 20 {
			t.Fatalf("unexpected list params: limit=%d offset=%d", repo.lastListLimit, repo.lastListOffset)
		}
	})
}

func TestHealthInsuranceCompaniesResource(t *testing.T) {
	id := uuidv7.MustBytes()
	createdBy := uuidv7.MustBytes()
	updatedBy := uuidv7.MustBytes()
	deletedBy := uuidv7.MustBytes()
	now := time.Now().UTC().Truncate(time.Second)
	deletedAt := now.Add(time.Hour)

	row := &health_insurance_companies.HealthInsuranceCompany{
		ID:           id,
		Name:         "Main",
		SortKey:      intPtrHICHandler(2),
		State:        "Berlin",
		Address:      "Street 1",
		MobileNumber: "+491234",
		Email:        "main@hic.local",
		IsActive:     true,
		CreatedAt:    now,
		CreatedBy:    createdBy,
		UpdatedAt:    now,
		UpdatedBy:    updatedBy,
		DeletedAt:    &deletedAt,
		DeletedBy:    deletedBy,
	}

	res := healthInsuranceCompanyResource(row, map[string]health_insurance_companies.StateDetail{
		"berlin": {
			FederalStateID: "019dfb03-afc1-7ad0-8606-715fe5fdc57f",
			FederalState:   "Berlin",
			LandName:       "Deutschland",
			LandISOCode:    "DE",
		},
	}, nil)
	if res.Type != "health_insurance_company" {
		t.Fatalf("unexpected type: %s", res.Type)
	}
	if res.Attributes.SortKey == nil || *res.Attributes.SortKey != 2 || !res.Attributes.IsActive {
		t.Fatalf("expected sortkey and is_active in resource")
	}
	if res.ID == "" || res.Attributes.CreatedBy == "" || res.Attributes.UpdatedBy == "" || res.Attributes.DeletedBy == "" {
		t.Fatalf("expected UUID fields in resource")
	}
	if res.Attributes.CreatedAt == "" || res.Attributes.UpdatedAt == "" || res.Attributes.DeletedAt == "" {
		t.Fatalf("expected time fields in resource")
	}
	if res.Attributes.FederalStateID == "" || res.Attributes.FederalState == "" || res.Attributes.LandName == "" || res.Attributes.LandISOCode == "" {
		t.Fatalf("expected federal state details in resource")
	}
}

func intPtrHICHandler(v int) *int { return &v }
