package handlers

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

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

	federalstate "wucher/internal/domain/federal_state"
	healthinsurancecompanies "wucher/internal/domain/health_insurance_companies"
	insurancepatientdata "wucher/internal/domain/insurance_patient_data"
	"wucher/internal/domain/opc"
	patientdata "wucher/internal/domain/patient_data"
	"wucher/internal/shared/pkg/uuidv7"
)

type patientDataServiceMock struct {
	createFn func(context.Context, *patientdata.PatientData) error
	updateFn func(context.Context, *patientdata.PatientData) error
	deleteFn func(context.Context, []byte, []byte) error
	getByID  func(context.Context, []byte) (*patientdata.PatientData, error)
	listFn   func(context.Context, string, string, int, int) ([]patientdata.PatientData, int64, error)
}

func (m *patientDataServiceMock) Create(ctx context.Context, row *patientdata.PatientData) error {
	if m.createFn != nil {
		return m.createFn(ctx, row)
	}
	return nil
}
func (m *patientDataServiceMock) Update(ctx context.Context, row *patientdata.PatientData) error {
	if m.updateFn != nil {
		return m.updateFn(ctx, row)
	}
	return nil
}
func (m *patientDataServiceMock) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
	if m.deleteFn != nil {
		return m.deleteFn(ctx, id, deletedBy)
	}
	return nil
}
func (m *patientDataServiceMock) GetByID(ctx context.Context, id []byte) (*patientdata.PatientData, error) {
	if m.getByID != nil {
		return m.getByID(ctx, id)
	}
	return nil, nil
}
func (m *patientDataServiceMock) List(ctx context.Context, filter, sort string, limit, offset int) ([]patientdata.PatientData, int64, error) {
	if m.listFn != nil {
		return m.listFn(ctx, filter, sort, limit, offset)
	}
	return nil, 0, nil
}

func mustNewUUIDString(t *testing.T) string {
	t.Helper()
	u := uuidv7.MustNew()
	s, err := uuidv7.String(u)
	if err != nil {
		t.Fatal(err)
	}
	return s
}

func newPatientDataApp(h *PatientDataHandler) *fiber.App {
	app := fiber.New()
	app.Post("/create", h.Create)
	app.Get("/get/:uuid", h.Get)
	app.Patch("/update/:uuid", h.Update)
	app.Delete("/delete/:uuid", h.Delete)
	app.Get("/list", h.List)
	app.Get("/dt", h.ListDatatable)
	return app
}

func TestPatientDataHandler_CRUDAndList(t *testing.T) {
	patientID := uuidv7.MustBytes()
	row := &patientdata.PatientData{
		ID:         patientID,
		FamilyName: "Doe",
		FirstName:  "John",
		Gender:     patientdata.GenderMale,
		CreatedAt:  time.Now().UTC(),
		UpdatedAt:  time.Now().UTC(),
	}
	svc := &patientDataServiceMock{
		createFn: func(_ context.Context, in *patientdata.PatientData) error {
			in.ID = patientID
			return nil
		},
		getByID: func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
			return row, nil
		},
		updateFn: func(_ context.Context, in *patientdata.PatientData) error {
			if in.FamilyName == "Updated" {
				row.FamilyName = in.FamilyName
			}
			return nil
		},
		listFn: func(_ context.Context, _, _ string, _, _ int) ([]patientdata.PatientData, int64, error) {
			return []patientdata.PatientData{*row}, 1, nil
		},
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	createBody := `{"data":{"type":"patient_data_create","attributes":{"family_name":"Doe","first_name":"John","gender":"male"}}}`
	req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(createBody))
	req.Header.Set("Content-Type", "application/json")
	res, err := app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusCreated {
		t.Fatalf("expected 201, got %d", res.StatusCode)
	}

	idStr, _ := uuidv7.BytesToString(patientID)
	req, _ = http.NewRequest(http.MethodGet, "/get/"+idStr, nil)
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}

	updateBody := `{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"family_name":"Updated"}}}`
	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(updateBody))
	req.Header.Set("Content-Type", "application/json")
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodGet, "/list?page[number]=2&page[size]=5&sort=family_name&filter[search]=abc", nil)
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodGet, "/dt?page=2&size=3&draw=7&search=abc", nil)
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}
}

func TestPatientDataHandler_ErrorPaths(t *testing.T) {
	idStr := mustNewUUIDString(t)
	idBytes, _ := uuidv7.ParseString(idStr)
	svc := &patientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
			return &patientdata.PatientData{ID: idBytes, FamilyName: "A", FirstName: "B", Gender: patientdata.GenderMale, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}, nil
		},
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	cases := []struct {
		name   string
		method string
		path   string
		body   string
		want   int
	}{
		{"create bad json", http.MethodPost, "/create", "{bad", http.StatusBadRequest},
		{"create required family", http.MethodPost, "/create", `{"data":{"type":"patient_data_create","attributes":{"family_name":" ","first_name":"A","gender":"male"}}}`, http.StatusUnprocessableEntity},
		{"create invalid birth date", http.MethodPost, "/create", `{"data":{"type":"patient_data_create","attributes":{"family_name":"A","first_name":"B","gender":"male","birth_date":"wrong"}}}`, http.StatusUnprocessableEntity},
		{"get invalid uuid", http.MethodGet, "/get/abc", "", http.StatusUnprocessableEntity},
		{"update invalid uuid", http.MethodPatch, "/update/abc", `{}`, http.StatusUnprocessableEntity},
		{"update bad json", http.MethodPatch, "/update/" + idStr, "{bad", http.StatusBadRequest},
		{"update mismatched id", http.MethodPatch, "/update/" + idStr, `{"data":{"type":"patient_data_update","id":"` + mustNewUUIDString(t) + `","attributes":{"family_name":"X"}}}`, http.StatusUnprocessableEntity},
		{"delete invalid uuid", http.MethodDelete, "/delete/abc", "", http.StatusUnprocessableEntity},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var bodyReader *strings.Reader
			if tc.body == "" {
				bodyReader = strings.NewReader("")
			} else {
				bodyReader = strings.NewReader(tc.body)
			}
			req, _ := http.NewRequest(tc.method, tc.path, bodyReader)
			req.Header.Set("Content-Type", "application/json")
			res, err := app.Test(req)
			if err != nil {
				t.Fatal(err)
			}
			if res.StatusCode != tc.want {
				t.Fatalf("expected %d, got %d", tc.want, res.StatusCode)
			}
		})
	}
}

func TestPatientDataHandler_ServiceErrors(t *testing.T) {
	idStr := mustNewUUIDString(t)
	idBytes, _ := uuidv7.ParseString(idStr)
	svc := &patientDataServiceMock{
		createFn: func(_ context.Context, _ *patientdata.PatientData) error { return errors.New("boom") },
		getByID: func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
			return nil, errors.New("not found")
		},
		updateFn: func(_ context.Context, _ *patientdata.PatientData) error { return errors.New("boom") },
		deleteFn: func(_ context.Context, _, _ []byte) error { return errors.New("boom") },
		listFn: func(_ context.Context, _, _ string, _, _ int) ([]patientdata.PatientData, int64, error) {
			return nil, 0, errors.New("boom")
		},
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(`{"data":{"type":"patient_data_create","attributes":{"family_name":"A","first_name":"B","gender":"male"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ := app.Test(req)
	if res.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodGet, "/get/"+idStr, nil)
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"patient_data_update","id":"`+idStr+`","attributes":{"family_name":"A"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", res.StatusCode)
	}

	svc.getByID = func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
		return &patientdata.PatientData{ID: idBytes, FamilyName: "A", FirstName: "B", Gender: patientdata.GenderMale, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}, nil
	}
	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"patient_data_update","id":"`+idStr+`","attributes":{"family_name":"A"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodDelete, "/delete/"+idStr, nil)
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", res.StatusCode)
	}

	svc.deleteFn = func(_ context.Context, _ []byte, _ []byte) error { return gorm.ErrRecordNotFound }
	req, _ = http.NewRequest(http.MethodDelete, "/delete/"+idStr, nil)
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodGet, "/list", nil)
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", res.StatusCode)
	}
}

func TestPatientDataHandler_ResourceHelpers(t *testing.T) {
	now := time.Now().UTC()
	birth := now.AddDate(-30, 0, 0)
	row := &patientdata.PatientData{
		ID:         uuidv7.MustBytes(),
		FamilyName: "Doe",
		FirstName:  "Jane",
		Gender:     patientdata.GenderFemale,
		BirthDate:  &birth,
		OPC: &opc.Opc{
			ID:    uuidv7.MustBytes(),
			Title: "OPC",
		},
		InsurancePatientData: &insurancepatientdata.InsurancePatientData{
			ID:                         uuidv7.MustBytes(),
			HealthInsuranceCompaniesId: uuidv7.MustBytes(),
			FederalStateId:             uuidv7.MustBytes(),
		},
		CreatedAt: now,
		UpdatedAt: now,
	}

	res := patientDataResource(row)
	if res.Type != "patient_data" {
		t.Fatalf("unexpected type: %s", res.Type)
	}
	if res.Attributes.Age == nil || *res.Attributes.Age < 0 {
		t.Fatalf("expected age to be set")
	}

	_ = computeAge(now.AddDate(-10, 0, 1))

	b, err := json.Marshal(res)
	if err != nil || len(b) == 0 {
		t.Fatalf("resource marshal failed: %v", err)
	}
}

func TestPatientDataHandler_UpdateValidationBranches(t *testing.T) {
	idStr := mustNewUUIDString(t)
	idBytes, _ := uuidv7.ParseString(idStr)
	baseRow := &patientdata.PatientData{ID: idBytes, FamilyName: "A", FirstName: "B", Gender: patientdata.GenderMale, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}
	svc := &patientDataServiceMock{
		getByID:  func(_ context.Context, _ []byte) (*patientdata.PatientData, error) { return baseRow, nil },
		updateFn: func(_ context.Context, _ *patientdata.PatientData) error { return nil },
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	tests := []string{
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{}}}`,
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"family_name":" "}}}`,
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"first_name":" "}}}`,
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"gender":"other"}}}`,
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"birth_date":"x"}}}`,
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"opc_id":"x"}}}`,
		`{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"insurance_patient_data_id":"x"}}}`,
	}
	for _, body := range tests {
		req := httptest.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		res, err := app.Test(req)
		if err != nil {
			t.Fatal(err)
		}
		if res.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d for %s", res.StatusCode, body)
		}
	}
}

func TestPatientDataHandler_CreateAdditionalBranches(t *testing.T) {
	baseID := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(baseID)
	svc := &patientDataServiceMock{
		createFn: func(_ context.Context, in *patientdata.PatientData) error {
			in.ID = baseID
			return nil
		},
		getByID: func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
			return nil, errors.New("fallback")
		},
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	t.Run("create with optional fields success", func(t *testing.T) {
		body := `{"data":{"type":"patient_data_create","attributes":{"family_name":"Doe","first_name":"John","gender":"male","co_insured":true,"birth_date":"2020-01-01","opc_id":"` + idStr + `","insurance_patient_data_id":"` + idStr + `","street":"A","svnr":"S","email":"e@x.com","phone":"1"}}}`
		req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		res, err := app.Test(req)
		if err != nil {
			t.Fatal(err)
		}
		if res.StatusCode != http.StatusCreated {
			t.Fatalf("expected 201, got %d", res.StatusCode)
		}
	})

	t.Run("create validator error", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(`{}`))
		req.Header.Set("Content-Type", "application/json")
		res, err := app.Test(req)
		if err != nil {
			t.Fatal(err)
		}
		if res.StatusCode != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422, got %d", res.StatusCode)
		}
	})

	for _, tc := range []struct {
		name, body string
	}{
		{"empty first name", `{"data":{"type":"patient_data_create","attributes":{"family_name":"Doe","first_name":" ","gender":"male"}}}`},
		{"bad opc", `{"data":{"type":"patient_data_create","attributes":{"family_name":"Doe","first_name":"John","gender":"male","opc_id":"bad"}}}`},

		{"bad insurance", `{"data":{"type":"patient_data_create","attributes":{"family_name":"Doe","first_name":"John","gender":"male","insurance_patient_data_id":"bad"}}}`},
	} {
		t.Run(tc.name, func(t *testing.T) {
			req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(tc.body))
			req.Header.Set("Content-Type", "application/json")
			res, err := app.Test(req)
			if err != nil {
				t.Fatal(err)
			}
			if res.StatusCode != http.StatusUnprocessableEntity {
				t.Fatalf("expected 422, got %d", res.StatusCode)
			}
		})
	}
}

func TestPatientDataHandler_DeleteAndListBranches(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	svc := &patientDataServiceMock{
		deleteFn: func(_ context.Context, _, _ []byte) error { return nil },
		listFn: func(_ context.Context, filter, sort string, limit, offset int) ([]patientdata.PatientData, int64, error) {
			if !((limit == 0 || limit == 20 || limit == 100) && offset == 0) {
				t.Fatalf("unexpected pagination limit=%d offset=%d", limit, offset)
			}
			_ = filter
			_ = sort
			return []patientdata.PatientData{}, 0, nil
		},
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	req, _ := http.NewRequest(http.MethodDelete, "/delete/"+idStr, nil)
	res, err := app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}

	for _, path := range []string{
		"/list?page[number]=-1&page[size]=1000",
		"/list?page=0&limit=0&size=0",
		"/dt?page=-1&size=1000",
		"/dt?page=0&limit=0&size=0",
	} {
		req, _ = http.NewRequest(http.MethodGet, path, nil)
		res, err = app.Test(req)
		if err != nil {
			t.Fatal(err)
		}
		if res.StatusCode != http.StatusOK {
			t.Fatalf("%s expected 200, got %d", path, res.StatusCode)
		}
	}
}

func TestPatientDataHandler_UpdateAdditionalBranches(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	svc := &patientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
			return &patientdata.PatientData{ID: id, FamilyName: "A", FirstName: "B", Gender: patientdata.GenderMale, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}, nil
		},
		updateFn: func(_ context.Context, _ *patientdata.PatientData) error { return nil },
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	req, _ := http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"patient_data_update","attributes":{"family_name":"X"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, err := app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422, got %d", res.StatusCode)
	}

	fullBody := `{"data":{"type":"patient_data_update","id":"` + idStr + `","attributes":{"family_name":"FN","first_name":"LN","gender":"female","birth_date":"2020-01-01","street":"S","svnr":"V","email":"a@b.com","phone":"1","co_insured":true,"opc_id":"` + idStr + `","insurance_patient_data_id":"` + idStr + `"}}}`
	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(fullBody))
	req.Header.Set("Content-Type", "application/json")
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"patient_data_update","id":"`+idStr+`","attributes":{"gender":"invalid"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422, got %d", res.StatusCode)
	}

	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"bad_type","id":"`+idStr+`","attributes":{"family_name":"FN"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, err = app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422, got %d", res.StatusCode)
	}
}

func TestPatientDataHandler_ListDatatableErrorAndResourceBranches(t *testing.T) {
	svc := &patientDataServiceMock{
		listFn: func(_ context.Context, _, _ string, _, _ int) ([]patientdata.PatientData, int64, error) {
			return nil, 0, errors.New("boom")
		},
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/dt", nil)
	res, err := app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", res.StatusCode)
	}

	row := &patientdata.PatientData{
		ID:         uuidv7.MustBytes(),
		FamilyName: "X",
		FirstName:  "Y",
		Gender:     patientdata.GenderMale,
		InsurancePatientData: &insurancepatientdata.InsurancePatientData{
			ID:                         uuidv7.MustBytes(),
			HealthInsuranceCompaniesId: uuidv7.MustBytes(),
			FederalStateId:             uuidv7.MustBytes(),
			HealthInsuranceCompanies: &healthinsurancecompanies.HealthInsuranceCompany{
				ID:           uuidv7.MustBytes(),
				Name:         "H",
				State:        "S",
				Address:      "A",
				MobileNumber: "M",
				Email:        "E",
			},
			FederalState: &federalstate.FederalState{
				ID:   uuidv7.MustBytes(),
				Name: "F",
			},
		},
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}
	resRow := patientDataResource(row)
	if resRow.Attributes.InsurancePatientData == nil ||
		resRow.Attributes.InsurancePatientData.HealthInsuranceCompany == nil ||
		resRow.Attributes.InsurancePatientData.FederalState == nil {
		t.Fatal("expected nested insurance resources to be mapped")
	}
}

func TestPatientDataHandler_UpdateFallbackResponseBranch(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	calls := 0
	svc := &patientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*patientdata.PatientData, error) {
			calls++
			if calls == 1 {
				return &patientdata.PatientData{
					ID:         id,
					FamilyName: "A",
					FirstName:  "B",
					Gender:     patientdata.GenderMale,
					CreatedAt:  time.Now().UTC(),
					UpdatedAt:  time.Now().UTC(),
				}, nil
			}
			return nil, errors.New("reload failed")
		},
		updateFn: func(_ context.Context, _ *patientdata.PatientData) error { return nil },
	}
	h := NewPatientDataHandler(svc)
	app := newPatientDataApp(h)

	req, _ := http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"patient_data_update","id":"`+idStr+`","attributes":{"family_name":"Z"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, err := app.Test(req)
	if err != nil {
		t.Fatal(err)
	}
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", res.StatusCode)
	}
}
