package handlers

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

	"github.com/gofiber/fiber/v2"

	flightinspection "wucher/internal/domain/flight_inspection"
	"wucher/internal/shared/pkg/uuidv7"
)

type flightInspectionServiceMock struct {
	createFn func(context.Context, *flightinspection.FlightInspection) error
	updateFn func(context.Context, *flightinspection.FlightInspection) error
	getFn    func(context.Context, []byte) (*flightinspection.FlightInspection, error)
	listFn   func(context.Context, string, int, int) ([]flightinspection.FlightInspection, int64, error)
}

func (m *flightInspectionServiceMock) CreateDraft(ctx context.Context, row *flightinspection.FlightInspection) error {
	if m.createFn != nil {
		return m.createFn(ctx, row)
	}
	return nil
}
func (m *flightInspectionServiceMock) Update(ctx context.Context, row *flightinspection.FlightInspection) error {
	if m.updateFn != nil {
		return m.updateFn(ctx, row)
	}
	return nil
}
func (m *flightInspectionServiceMock) GetByID(ctx context.Context, id []byte) (*flightinspection.FlightInspection, error) {
	if m.getFn != nil {
		return m.getFn(ctx, id)
	}
	return nil, nil
}
func (m *flightInspectionServiceMock) List(ctx context.Context, sort string, limit, offset int) ([]flightinspection.FlightInspection, int64, error) {
	if m.listFn != nil {
		return m.listFn(ctx, sort, limit, offset)
	}
	return nil, 0, nil
}

func newFIApp(h *FlightInspectionHandler) *fiber.App {
	app := fiber.New()
	app.Use(func(c *fiber.Ctx) error {
		c.Locals("user_id", uuidv7.MustBytes())
		return c.Next()
	})
	app.Post("/fi", h.Create)
	app.Get("/fi/:id", h.GetByID)
	app.Patch("/fi/:id", h.Update)
	app.Get("/fi", h.List)
	return app
}

func doReqFI(t *testing.T, app *fiber.App, method, path string, body []byte) (int, string) {
	t.Helper()
	var rdr io.Reader
	if body != nil {
		rdr = bytes.NewReader(body)
	}
	req, _ := http.NewRequest(method, path, rdr)
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	return resp.StatusCode, string(b)
}

func TestFlightInspectionHandler(t *testing.T) {
	t.Run("new", func(t *testing.T) {
		h := NewFlightInspectionHandler(&flightInspectionServiceMock{})
		if h == nil || h.validate == nil {
			t.Fatal("expected initialized")
		}
	})

	t.Run("create branches", func(t *testing.T) {
		h := NewFlightInspectionHandler(&flightInspectionServiceMock{})
		code, _ := doReqFI(t, newFIApp(h), http.MethodPost, "/fi", []byte(`{"data":`))
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(h), http.MethodPost, "/fi", []byte(`{}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(h), http.MethodPost, "/fi", []byte(`{"data":{"type":"flight_inspection","attributes":{"inspection_date":"x"}}}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}
		hErr := NewFlightInspectionHandler(&flightInspectionServiceMock{
			createFn: func(context.Context, *flightinspection.FlightInspection) error { return errors.New("x") },
		})
		code, _ = doReqFI(t, newFIApp(hErr), http.MethodPost, "/fi", []byte(`{"data":{"type":"flight_inspection","attributes":{"inspection_date":"2026-04-09"}}}`))
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}
		hOK := NewFlightInspectionHandler(&flightInspectionServiceMock{
			createFn: func(_ context.Context, row *flightinspection.FlightInspection) error {
				row.ID = uuidv7.MustBytes()
				row.Status = flightinspection.StatusDraft
				row.CreatedAt = time.Now().UTC()
				row.UpdatedAt = row.CreatedAt
				return nil
			},
		})
		code, _ = doReqFI(t, newFIApp(hOK), http.MethodPost, "/fi", []byte(`{"data":{"type":"flight_inspection","attributes":{"inspection_date":"2026-04-09"}}}`))
		if code != http.StatusCreated {
			t.Fatalf("expected 201 got %d", code)
		}
	})

	t.Run("get branches", func(t *testing.T) {
		h := NewFlightInspectionHandler(&flightInspectionServiceMock{})
		code, _ := doReqFI(t, newFIApp(h), http.MethodGet, "/fi/bad", nil)
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}
		id := mustUUIDStringHospital(t, uuidv7.MustBytes())
		hErr := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return nil, errors.New("x") },
		})
		code, _ = doReqFI(t, newFIApp(hErr), http.MethodGet, "/fi/"+id, nil)
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}
		hNil := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return nil, nil },
		})
		code, _ = doReqFI(t, newFIApp(hNil), http.MethodGet, "/fi/"+id, nil)
		if code != http.StatusNotFound {
			t.Fatalf("expected 404 got %d", code)
		}
		hOK := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) {
				return &flightinspection.FlightInspection{
					ID:             uuidv7.MustBytes(),
					InspectionDate: time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC),
					Status:         flightinspection.StatusDraft,
					CreatedAt:      time.Now().UTC(),
					UpdatedAt:      time.Now().UTC(),
				}, nil
			},
		})
		code, body := doReqFI(t, newFIApp(hOK), http.MethodGet, "/fi/"+id, nil)
		if code != http.StatusOK || !strings.Contains(body, `"flight_inspection"`) {
			t.Fatalf("expected 200 got %d body=%s", code, body)
		}
	})

	t.Run("update branches", func(t *testing.T) {
		h := NewFlightInspectionHandler(&flightInspectionServiceMock{})
		code, _ := doReqFI(t, newFIApp(h), http.MethodPatch, "/fi/bad", []byte(`{}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}
		id := mustUUIDStringHospital(t, uuidv7.MustBytes())
		code, _ = doReqFI(t, newFIApp(h), http.MethodPatch, "/fi/"+id, []byte(`{"data":`))
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(h), http.MethodPatch, "/fi/"+id, []byte(`{}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(h), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"","attributes":{"status":"Draft"}}}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(h), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{}}}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}

		hGetErr := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return nil, errors.New("x") },
		})
		code, _ = doReqFI(t, newFIApp(hGetErr), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"status":"Draft"}}}`))
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}

		hNot := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return nil, nil },
		})
		code, _ = doReqFI(t, newFIApp(hNot), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"status":"Draft"}}}`))
		if code != http.StatusNotFound {
			t.Fatalf("expected 404 got %d", code)
		}

		row := &flightinspection.FlightInspection{ID: uuidv7.MustBytes(), CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), Status: flightinspection.StatusDraft}
		hBadDate := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return row, nil },
		})
		code, _ = doReqFI(t, newFIApp(hBadDate), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"inspection_date":"x"}}}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}

		hBadStatus := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return row, nil },
		})
		code, _ = doReqFI(t, newFIApp(hBadStatus), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"status":"BAD"}}}`))
		if code != http.StatusUnprocessableEntity {
			t.Fatalf("expected 422 got %d", code)
		}

		hUpdErr := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn:    func(context.Context, []byte) (*flightinspection.FlightInspection, error) { return row, nil },
			updateFn: func(context.Context, *flightinspection.FlightInspection) error { return errors.New("x") },
		})
		code, _ = doReqFI(t, newFIApp(hUpdErr), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"status":"Draft"}}}`))
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}

		hOK := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(_ context.Context, _ []byte) (*flightinspection.FlightInspection, error) {
				return &flightinspection.FlightInspection{
					ID:             uuidv7.MustBytes(),
					InspectionDate: time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC),
					Status:         flightinspection.StatusDraft,
					CreatedAt:      time.Now().UTC(),
					UpdatedAt:      time.Now().UTC(),
				}, nil
			},
			updateFn: func(context.Context, *flightinspection.FlightInspection) error { return nil },
		})
		code, _ = doReqFI(t, newFIApp(hOK), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"status":"Completed"}}}`))
		if code != http.StatusOK {
			t.Fatalf("expected 200 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(hOK), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"inspection_date":"2026-04-10"}}}`))
		if code != http.StatusOK {
			t.Fatalf("expected 200 got %d", code)
		}

		calls := 0
		hFallback := NewFlightInspectionHandler(&flightInspectionServiceMock{
			getFn: func(_ context.Context, _ []byte) (*flightinspection.FlightInspection, error) {
				calls++
				if calls == 1 {
					return &flightinspection.FlightInspection{
						ID:             uuidv7.MustBytes(),
						InspectionDate: time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC),
						Status:         flightinspection.StatusDraft,
						CreatedAt:      time.Now().UTC(),
						UpdatedAt:      time.Now().UTC(),
					}, nil
				}
				return nil, errors.New("reload failed")
			},
			updateFn: func(context.Context, *flightinspection.FlightInspection) error { return nil },
		})
		code, _ = doReqFI(t, newFIApp(hFallback), http.MethodPatch, "/fi/"+id, []byte(`{"data":{"type":"flight_inspection","id":"`+id+`","attributes":{"status":"InProgress"}}}`))
		if code != http.StatusOK {
			t.Fatalf("expected 200 got %d", code)
		}
	})

	t.Run("list branches and resource helper", func(t *testing.T) {
		hErr := NewFlightInspectionHandler(&flightInspectionServiceMock{
			listFn: func(context.Context, string, int, int) ([]flightinspection.FlightInspection, int64, error) {
				return nil, 0, errors.New("x")
			},
		})
		code, _ := doReqFI(t, newFIApp(hErr), http.MethodGet, "/fi?page[number]=0&page[size]=200", nil)
		if code != http.StatusBadRequest {
			t.Fatalf("expected 400 got %d", code)
		}

		hOK := NewFlightInspectionHandler(&flightInspectionServiceMock{
			listFn: func(_ context.Context, _ string, limit, offset int) ([]flightinspection.FlightInspection, int64, error) {
				if offset != 0 {
					t.Fatalf("unexpected page args")
				}
				return []flightinspection.FlightInspection{{
					ID:             uuidv7.MustBytes(),
					InspectionDate: time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC),
					Status:         flightinspection.StatusDraft,
					CreatedAt:      time.Now().UTC(),
					UpdatedAt:      time.Now().UTC(),
				}}, 1, nil
			},
		})
		code, _ = doReqFI(t, newFIApp(hOK), http.MethodGet, "/fi?page[number]=0&page[size]=200", nil)
		if code != http.StatusOK {
			t.Fatalf("expected 200 got %d", code)
		}
		code, _ = doReqFI(t, newFIApp(hOK), http.MethodGet, "/fi?page[number]=-1&page[size]=-1", nil)
		if code != http.StatusOK {
			t.Fatalf("expected 200 got %d", code)
		}

		now := time.Now().UTC()
		ref := flightInspectionResource(&flightinspection.FlightInspection{
			ID:             uuidv7.MustBytes(),
			InspectionDate: now,
			Status:         flightinspection.StatusInProgress,
			CreatedAt:      now,
			UpdatedAt:      now,
		})
		if ref.Type != "flight_inspection" || ref.Attributes.Status == "" {
			t.Fatal("expected resource mapped")
		}
	})
}
