package handlers

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

	"github.com/gofiber/fiber/v2"

	otherperson "wucher/internal/domain/other_person"
	"wucher/internal/shared/pkg/uuidv7"
)

type fakeOtherPersonService struct {
	rows  []otherperson.OtherPerson
	total int64
	err   error

	lastQ      string
	lastLimit  int
	lastOffset int

	lastCreate   *otherperson.OtherPerson
	lastUpdate   *otherperson.OtherPerson
	lastDeleteID []byte
}

func (f *fakeOtherPersonService) Upsert(context.Context, *otherperson.OtherPerson) error { return nil }

func (f *fakeOtherPersonService) Create(_ context.Context, p *otherperson.OtherPerson) error {
	f.lastCreate = p
	if f.err != nil {
		return f.err
	}
	if len(p.ID) == 0 {
		p.ID = uuidv7.MustBytes()
	}
	return nil
}

func (f *fakeOtherPersonService) Update(_ context.Context, p *otherperson.OtherPerson) error {
	f.lastUpdate = p
	return f.err
}

func (f *fakeOtherPersonService) Delete(_ context.Context, id, actor []byte) error {
	f.lastDeleteID = id
	return f.err
}

func (f *fakeOtherPersonService) GetByID(_ context.Context, id []byte) (*otherperson.OtherPerson, error) {
	if f.err != nil {
		return nil, f.err
	}
	if len(f.rows) == 0 {
		return nil, nil
	}
	row := f.rows[0]
	return &row, nil
}
func (f *fakeOtherPersonService) Search(_ context.Context, q string, limit, offset int) ([]otherperson.OtherPerson, int64, error) {
	f.lastQ, f.lastLimit, f.lastOffset = q, limit, offset
	if f.err != nil {
		return nil, 0, f.err
	}
	return f.rows, f.total, nil
}

func TestOtherPersonHandlerSearch(t *testing.T) {
	id := uuidv7.MustBytes()
	svc := &fakeOtherPersonService{
		rows: []otherperson.OtherPerson{
			{ID: id, Name: "Guest One", MobilePhone: "+43-1", Email: "a@x.io"},
		},
		total: 1,
	}
	h := NewOtherPersonHandler(svc)

	app := fiber.New()
	app.Get("/api/v1/contact/other", h.Search)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/contact/other?q=guest&limit=5&offset=2", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if svc.lastQ != "guest" || svc.lastLimit != 5 || svc.lastOffset != 2 {
		t.Fatalf("query not forwarded: q=%q limit=%d offset=%d", svc.lastQ, svc.lastLimit, svc.lastOffset)
	}

	body, _ := io.ReadAll(resp.Body)
	var out struct {
		Data []struct {
			Type       string `json:"type"`
			ID         string `json:"id"`
			Attributes struct {
				Name        string `json:"name"`
				MobilePhone string `json:"mobile_phone"`
				Email       string `json:"email"`
			} `json:"attributes"`
		} `json:"data"`
		Meta struct {
			Total int64 `json:"total"`
		} `json:"meta"`
	}
	if err := json.Unmarshal(body, &out); err != nil {
		t.Fatalf("unmarshal: %v (%s)", err, string(body))
	}
	if out.Meta.Total != 1 {
		t.Fatalf("expected meta.total 1, got %d", out.Meta.Total)
	}
	if len(out.Data) != 1 {
		t.Fatalf("expected 1 row, got %d", len(out.Data))
	}
	d := out.Data[0]
	if d.Type != "other_person" || d.ID != idString(id) {
		t.Fatalf("unexpected resource type/id: %q/%q", d.Type, d.ID)
	}
	if d.Attributes.Name != "Guest One" || d.Attributes.MobilePhone != "+43-1" || d.Attributes.Email != "a@x.io" {
		t.Fatalf("unexpected attributes: %+v", d.Attributes)
	}
}

func TestOtherPersonHandlerCreate(t *testing.T) {
	svc := &fakeOtherPersonService{}
	h := NewOtherPersonHandler(svc)
	app := fiber.New()
	app.Post("/api/v1/contact/other/create", h.Create)

	body := `{"data":{"type":"other_person","attributes":{"name":"New Guest","mobile_phone":"+43-9","email":"new@x.io"}}}`
	req, _ := http.NewRequest(http.MethodPost, "/api/v1/contact/other/create", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("expected 201, got %d", resp.StatusCode)
	}
	if svc.lastCreate == nil || svc.lastCreate.Name != "New Guest" || svc.lastCreate.Email != "new@x.io" {
		t.Fatalf("service not called with attributes: %+v", svc.lastCreate)
	}

	out := struct {
		Data struct {
			Type       string `json:"type"`
			Attributes struct {
				Name string `json:"name"`
			} `json:"attributes"`
		} `json:"data"`
	}{}
	raw, _ := io.ReadAll(resp.Body)
	if err := json.Unmarshal(raw, &out); err != nil {
		t.Fatalf("unmarshal: %v (%s)", err, string(raw))
	}
	if out.Data.Type != "other_person" || out.Data.Attributes.Name != "New Guest" {
		t.Fatalf("unexpected response: %s", string(raw))
	}
}

func TestOtherPersonHandlerCreateDuplicateConflict(t *testing.T) {
	svc := &fakeOtherPersonService{err: otherperson.ErrDuplicate}
	h := NewOtherPersonHandler(svc)
	app := fiber.New()
	app.Post("/api/v1/contact/other/create", h.Create)

	body := `{"data":{"attributes":{"name":"Dup"}}}`
	req, _ := http.NewRequest(http.MethodPost, "/api/v1/contact/other/create", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422 for duplicate, got %d", resp.StatusCode)
	}
}

func TestOtherPersonHandlerUpdate(t *testing.T) {
	id := uuidv7.MustBytes()
	svc := &fakeOtherPersonService{
		rows: []otherperson.OtherPerson{{ID: id, Name: "Edited", MobilePhone: "+43-1", Email: "e@x.io"}},
	}
	h := NewOtherPersonHandler(svc)
	app := fiber.New()
	app.Patch("/api/v1/contact/other/update/:id", h.Update)

	idStr, _ := uuidv7.BytesToString(id)
	body := `{"data":{"attributes":{"name":"Edited","mobile_phone":"+43-1","email":"e@x.io"}}}`
	req, _ := http.NewRequest(http.MethodPatch, "/api/v1/contact/other/update/"+idStr, strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if svc.lastUpdate == nil || string(svc.lastUpdate.ID) != string(id) || svc.lastUpdate.Name != "Edited" {
		t.Fatalf("update not forwarded: %+v", svc.lastUpdate)
	}
}

func TestOtherPersonHandlerUpdateNotFound(t *testing.T) {
	svc := &fakeOtherPersonService{err: otherperson.ErrNotFound}
	h := NewOtherPersonHandler(svc)
	app := fiber.New()
	app.Patch("/api/v1/contact/other/update/:id", h.Update)

	idStr, _ := uuidv7.BytesToString(uuidv7.MustBytes())
	body := `{"data":{"attributes":{"name":"X"}}}`
	req, _ := http.NewRequest(http.MethodPatch, "/api/v1/contact/other/update/"+idStr, strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", resp.StatusCode)
	}
}

func TestOtherPersonHandlerDelete(t *testing.T) {
	id := uuidv7.MustBytes()
	svc := &fakeOtherPersonService{}
	h := NewOtherPersonHandler(svc)
	app := fiber.New()
	app.Delete("/api/v1/contact/other/delete/:id", h.Delete)

	idStr, _ := uuidv7.BytesToString(id)
	req, _ := http.NewRequest(http.MethodDelete, "/api/v1/contact/other/delete/"+idStr, nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if string(svc.lastDeleteID) != string(id) {
		t.Fatalf("delete id not forwarded")
	}
}

func TestOtherPersonHandlerGetNotFound(t *testing.T) {
	svc := &fakeOtherPersonService{} // rows empty -> GetByID returns nil,nil
	h := NewOtherPersonHandler(svc)
	app := fiber.New()
	app.Get("/api/v1/contact/other/get/:id", h.Get)

	idStr, _ := uuidv7.BytesToString(uuidv7.MustBytes())
	req, _ := http.NewRequest(http.MethodGet, "/api/v1/contact/other/get/"+idStr, nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request: %v", err)
	}
	if resp.StatusCode != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", resp.StatusCode)
	}
}
