package handlers

import (
	"context"
	"errors"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"

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

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

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

func newInsurancePatientDataApp(h *InsurancePatientDataHandler) *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 TestInsurancePatientDataHandler_CRUDAndLists(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	row := &insurancepatientdata.InsurancePatientData{
		ID:                         id,
		HealthInsuranceCompaniesId: id,
		FederalStateId:             id,
		CreatedAt:                  time.Now().UTC(),
		UpdatedAt:                  time.Now().UTC(),
	}
	svc := &insurancePatientDataServiceMock{
		createFn: func(_ context.Context, in *insurancepatientdata.InsurancePatientData) error {
			in.ID = id
			return nil
		},
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) {
			return row, nil
		},
		updateFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error { return nil },
		deleteFn: func(_ context.Context, _, _ []byte) error { return nil },
		listFn: func(_ context.Context, _, _ string, _, _ int) ([]insurancepatientdata.InsurancePatientData, int64, error) {
			return []insurancepatientdata.InsurancePatientData{*row}, 1, nil
		},
	}
	h := NewInsurancePatientDataHandler(svc)
	app := newInsurancePatientDataApp(h)

	req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(`{"data":{"type":"insurance_patient_data_create","attributes":{"health_insurance_companies_id":"`+idStr+`","federal_state_id":"`+idStr+`"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ := app.Test(req)
	if res.StatusCode != http.StatusCreated {
		t.Fatalf("expected 201 got %d", res.StatusCode)
	}

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

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

	req, _ = http.NewRequest(http.MethodDelete, "/delete/"+idStr, nil)
	res, _ = app.Test(req)
	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=created_at&filter[search]=x", nil)
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusOK {
		t.Fatalf("expected 200 got %d", res.StatusCode)
	}

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

func TestInsurancePatientDataHandler_ErrorBranches(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	svc := &insurancePatientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) {
			return nil, errors.New("not found")
		},
		createFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error { return errors.New("boom") },
		updateFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error { return errors.New("boom") },
		deleteFn: func(_ context.Context, _, _ []byte) error { return errors.New("boom") },
		listFn: func(_ context.Context, _, _ string, _, _ int) ([]insurancepatientdata.InsurancePatientData, int64, error) {
			return nil, 0, errors.New("boom")
		},
	}
	h := NewInsurancePatientDataHandler(svc)
	app := newInsurancePatientDataApp(h)

	cases := []struct {
		method, path, body string
		want               int
	}{
		{http.MethodPost, "/create", "{bad", 400},
		{http.MethodPost, "/create", `{"data":{"type":"insurance_patient_data_create","attributes":{"health_insurance_companies_id":"bad","federal_state_id":"` + idStr + `"}}}`, 422},
		{http.MethodPost, "/create", `{"data":{"type":"insurance_patient_data_create","attributes":{"health_insurance_companies_id":"` + idStr + `","federal_state_id":"bad"}}}`, 422},
		{http.MethodPost, "/create", `{"data":{"type":"insurance_patient_data_create","attributes":{"health_insurance_companies_id":"` + idStr + `","federal_state_id":"` + idStr + `"}}}`, 400},
		{http.MethodGet, "/get/bad", "", 422},
		{http.MethodGet, "/get/" + idStr, "", 404},
		{http.MethodPatch, "/update/bad", `{}`, 422},
		{http.MethodPatch, "/update/" + idStr, "{bad", 400},
		{http.MethodPatch, "/update/" + idStr, `{"data":{"type":"insurance_patient_data_update","id":"","attributes":{}}}`, 422},
		{http.MethodPatch, "/update/" + idStr, `{"data":{"type":"insurance_patient_data_update","id":"` + mustNewUUIDForReserve(t) + `","attributes":{}}}`, 422},
		{http.MethodPatch, "/update/" + idStr, `{"data":{"type":"insurance_patient_data_update","id":"` + idStr + `","attributes":{}}}`, 422},
		{http.MethodDelete, "/delete/bad", "", 422},
		{http.MethodDelete, "/delete/" + idStr, "", 400},
		{http.MethodGet, "/list", "", 400},
		{http.MethodGet, "/dt", "", 400},
	}

	for _, tc := range cases {
		req, _ := http.NewRequest(tc.method, tc.path, 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 != tc.want {
			t.Fatalf("%s %s expected %d got %d", tc.method, tc.path, tc.want, res.StatusCode)
		}
	}
}

func TestInsurancePatientDataHandler_UpdateValidationSpecific(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	svc := &insurancePatientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) {
			return &insurancepatientdata.InsurancePatientData{
				ID:                         id,
				HealthInsuranceCompaniesId: id,
				FederalStateId:             id,
				CreatedAt:                  time.Now().UTC(),
				UpdatedAt:                  time.Now().UTC(),
			}, nil
		},
		updateFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error { return nil },
	}
	h := NewInsurancePatientDataHandler(svc)
	app := newInsurancePatientDataApp(h)

	for _, body := range []string{
		`{"data":{"type":"insurance_patient_data_update","id":"` + idStr + `","attributes":{"health_insurance_companies_id":"bad"}}}`,
		`{"data":{"type":"insurance_patient_data_update","id":"` + idStr + `","attributes":{"federal_state_id":"bad"}}}`,
	} {
		req, _ := http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		res, _ := app.Test(req)
		if res.StatusCode != 422 {
			t.Fatalf("expected 422 got %d body=%s", res.StatusCode, body)
		}
	}
}

func TestInsurancePatientDataResource(t *testing.T) {
	id := uuidv7.MustBytes()
	row := &insurancepatientdata.InsurancePatientData{
		ID:                         id,
		HealthInsuranceCompaniesId: id,
		FederalStateId:             id,
		HealthInsuranceCompanies: &healthinsurancecompanies.HealthInsuranceCompany{
			ID:           id,
			Name:         "H",
			State:        "S",
			Address:      "A",
			MobileNumber: "M",
			Email:        "E",
		},
		FederalState: &federalstate.FederalState{
			ID:   id,
			Name: "F",
		},
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}
	res := insurancePatientDataResource(row)
	if res.Type != "insurance_patient_data" || res.Attributes.HealthInsuranceCompany == nil || res.Attributes.FederalState == nil {
		t.Fatal("resource mapping failed")
	}
}

func TestInsurancePatientDataHandler_FallbackAndPaginationBranches(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	svc := &insurancePatientDataServiceMock{
		createFn: func(_ context.Context, row *insurancepatientdata.InsurancePatientData) error {
			row.ID = id
			return nil
		},
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) {
			// cover Create fallback path
			return nil, errors.New("fallback")
		},
		updateFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error { return nil },
		listFn: func(_ context.Context, _ string, _ string, limit, offset int) ([]insurancepatientdata.InsurancePatientData, int64, error) {
			if limit < 1 || limit > 100 || offset < 0 {
				t.Fatalf("unexpected pagination limit=%d offset=%d", limit, offset)
			}
			return []insurancepatientdata.InsurancePatientData{}, 0, nil
		},
	}
	h := NewInsurancePatientDataHandler(svc)
	app := newInsurancePatientDataApp(h)

	req, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(`{"data":{"type":"insurance_patient_data_create","attributes":{"health_insurance_companies_id":"`+idStr+`","federal_state_id":"`+idStr+`"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ := app.Test(req)
	if res.StatusCode != http.StatusCreated {
		t.Fatalf("expected 201 got %d", res.StatusCode)
	}

	// separate service for Update fallback branch: existing found, updated fetch fails
	updateCalls := 0
	h.svc = &insurancePatientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) {
			updateCalls++
			if updateCalls == 1 {
				return &insurancepatientdata.InsurancePatientData{
					ID:                         id,
					HealthInsuranceCompaniesId: id,
					FederalStateId:             id,
					CreatedAt:                  time.Now().UTC(),
					UpdatedAt:                  time.Now().UTC(),
				}, nil
			}
			if updateCalls == 2 {
				return nil, errors.New("fallback")
			}
			return &insurancepatientdata.InsurancePatientData{
				ID:                         id,
				HealthInsuranceCompaniesId: id,
				FederalStateId:             id,
				CreatedAt:                  time.Now().UTC(),
				UpdatedAt:                  time.Now().UTC(),
			}, nil
		},
		updateFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error { return nil },
		listFn:   svc.listFn,
	}

	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"insurance_patient_data_update","id":"`+idStr+`","attributes":{"health_insurance_companies_id":"`+idStr+`"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ = app.Test(req)
	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, _ = app.Test(req)
		if res.StatusCode != http.StatusOK {
			t.Fatalf("%s expected 200 got %d", path, res.StatusCode)
		}
	}
}

func TestInsurancePatientDataHandler_CreateValidateAndUpdateFailedBranch(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)
	svc := &insurancePatientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) {
			return &insurancepatientdata.InsurancePatientData{
				ID:                         id,
				HealthInsuranceCompaniesId: id,
				FederalStateId:             id,
				CreatedAt:                  time.Now().UTC(),
				UpdatedAt:                  time.Now().UTC(),
			}, nil
		},
		updateFn: func(_ context.Context, _ *insurancepatientdata.InsurancePatientData) error {
			return errors.New("update failed")
		},
	}
	h := NewInsurancePatientDataHandler(svc)
	app := newInsurancePatientDataApp(h)

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

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

func TestInsurancePatientDataHandler_UpdateValidatorAndNilExistingBranches(t *testing.T) {
	id := uuidv7.MustBytes()
	idStr, _ := uuidv7.BytesToString(id)

	// validator error path
	svc := &insurancePatientDataServiceMock{}
	h := NewInsurancePatientDataHandler(svc)
	app := newInsurancePatientDataApp(h)
	req, _ := http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"bad_type","id":"`+idStr+`","attributes":{"health_insurance_companies_id":"`+idStr+`"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ := app.Test(req)
	if res.StatusCode != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422 got %d", res.StatusCode)
	}

	// existing == nil path
	h.svc = &insurancePatientDataServiceMock{
		getByID: func(_ context.Context, _ []byte) (*insurancepatientdata.InsurancePatientData, error) { return nil, nil },
	}
	req, _ = http.NewRequest(http.MethodPatch, "/update/"+idStr, strings.NewReader(`{"data":{"type":"insurance_patient_data_update","id":"`+idStr+`","attributes":{"health_insurance_companies_id":"`+idStr+`"}}}`))
	req.Header.Set("Content-Type", "application/json")
	res, _ = app.Test(req)
	if res.StatusCode != http.StatusNotFound {
		t.Fatalf("expected 404 got %d", res.StatusCode)
	}
}
