package iom

import (
	"bytes"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"system-altrak/internal/domain"

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

type iomServiceMock struct {
	createErr   error
	createCalls int
	createInput *domain.Memorandum

	getErr    error
	getCalls  int
	getID     uint
	getBranch uint
	getRole   string
	getMemo   *domain.Memorandum

	listErr    error
	listCalls  int
	listBranch uint
	listRole   string
	listMemos  []domain.Memorandum

	pdfErr    error
	pdfCalls  int
	pdfID     uint
	pdfBranch uint
	pdfRole   string
	pdfBytes  []byte
	pdfName   string

	excelErr    error
	excelCalls  int
	excelBranch uint
	excelRole   string
	excelBytes  []byte

	deleteErr    error
	deleteCalls  int
	deleteID     uint
	deleteBranch uint
	deleteRole   string
}

func (m *iomServiceMock) CreateMemorandum(memo *domain.Memorandum) error {
	m.createCalls++
	m.createInput = memo
	return m.createErr
}

func (m *iomServiceMock) GetMemorandumByID(id uint, branchID uint, role string) (*domain.Memorandum, error) {
	m.getCalls++
	m.getID = id
	m.getBranch = branchID
	m.getRole = role
	if m.getErr != nil {
		return nil, m.getErr
	}
	if m.getMemo != nil {
		return m.getMemo, nil
	}
	return &domain.Memorandum{Ref: "IOM-001", Subject: "TEST"}, nil
}

func (m *iomServiceMock) ListMemorandums(branchID uint, role string) ([]domain.Memorandum, error) {
	m.listCalls++
	m.listBranch = branchID
	m.listRole = role
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.listMemos, nil
}

func (m *iomServiceMock) ExportMemorandumsExcel(branchID uint, role string) ([]byte, error) {
	m.excelCalls++
	m.excelBranch = branchID
	m.excelRole = role
	if m.excelErr != nil {
		return nil, m.excelErr
	}
	return m.excelBytes, nil
}

func (m *iomServiceMock) SynthesizePdf(id uint, branchID uint, role string) ([]byte, string, error) {
	m.pdfCalls++
	m.pdfID = id
	m.pdfBranch = branchID
	m.pdfRole = role
	if m.pdfErr != nil {
		return nil, "", m.pdfErr
	}
	return m.pdfBytes, m.pdfName, nil
}

func (m *iomServiceMock) DeleteMemorandum(id uint, branchID uint, role string) error {
	m.deleteCalls++
	m.deleteID = id
	m.deleteBranch = branchID
	m.deleteRole = role
	return m.deleteErr
}

func buildIOMHandlerTestApp(h *Handler) *fiber.App {
	app := fiber.New()
	withAuth := func(c *fiber.Ctx) {
		c.Locals("user_id", uint(88))
		c.Locals("current_branch_id", uint(7))
		c.Locals("role", "admin")
	}

	app.Post("/iom", func(c *fiber.Ctx) error {
		withAuth(c)
		return h.CreateMemorandum(c)
	})
	app.Get("/iom/export/excel", func(c *fiber.Ctx) error {
		withAuth(c)
		return h.ExportExcel(c)
	})
	app.Get("/iom/:id/pdf", func(c *fiber.Ctx) error {
		withAuth(c)
		return h.DownloadPdf(c)
	})
	app.Get("/iom/:id", func(c *fiber.Ctx) error {
		withAuth(c)
		return h.GetMemorandum(c)
	})
	app.Get("/iom", func(c *fiber.Ctx) error {
		withAuth(c)
		return h.ListMemorandums(c)
	})
	app.Delete("/iom/:id", func(c *fiber.Ctx) error {
		withAuth(c)
		return h.DeleteMemorandum(c)
	})

	return app
}

func TestCreateMemorandumRejectsInvalidJSON(t *testing.T) {
	svc := &iomServiceMock{}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodPost, "/iom", bytes.NewBufferString("{"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if svc.createCalls != 0 {
		t.Fatalf("expected create calls 0, got %d", svc.createCalls)
	}
}

func TestCreateMemorandumRejectsValidationFailure(t *testing.T) {
	svc := &iomServiceMock{}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodPost, "/iom", bytes.NewBufferString(`{"to_department":"A"}`))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if svc.createCalls != 0 {
		t.Fatalf("expected create calls 0, got %d", svc.createCalls)
	}
}

func TestCreateMemorandumDelegatesScopeAndPayload(t *testing.T) {
	svc := &iomServiceMock{}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	payload := `{"to_department":"Service","from_department":"Parts","subject":"Memo","body":"Isi memo","signed_by":"Supervisor"}`
	req := httptest.NewRequest(http.MethodPost, "/iom", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
	}
	if svc.createCalls != 1 || svc.createInput == nil {
		t.Fatalf("expected create called once with payload, calls=%d", svc.createCalls)
	}
	if svc.createInput.CreatedBy == nil || *svc.createInput.CreatedBy != 88 {
		t.Fatalf("expected CreatedBy=88, got %+v", svc.createInput.CreatedBy)
	}
	if svc.createInput.BranchID != 7 {
		t.Fatalf("expected BranchID=7, got %d", svc.createInput.BranchID)
	}
	if svc.createInput.ToDepartment != "Service" || svc.createInput.FromDepartment != "Parts" {
		t.Fatalf("unexpected department payload: to=%q from=%q", svc.createInput.ToDepartment, svc.createInput.FromDepartment)
	}
}

func TestGetMemorandumRejectsInvalidID(t *testing.T) {
	svc := &iomServiceMock{}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom/abc", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if svc.getCalls != 0 {
		t.Fatalf("expected get calls 0, got %d", svc.getCalls)
	}
}

func TestGetMemorandumMapsServiceErrorToNotFound(t *testing.T) {
	svc := &iomServiceMock{getErr: gorm.ErrRecordNotFound}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom/12", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusNotFound {
		t.Fatalf("expected %d, got %d", fiber.StatusNotFound, resp.StatusCode)
	}
	if svc.getID != 12 || svc.getBranch != 7 || svc.getRole != "admin" {
		t.Fatalf("unexpected get delegation: id=%d branch=%d role=%q", svc.getID, svc.getBranch, svc.getRole)
	}
}

func TestListMemorandumsDelegatesScope(t *testing.T) {
	svc := &iomServiceMock{listMemos: []domain.Memorandum{{Ref: "IOM-001"}}}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
	}
	if svc.listCalls != 1 || svc.listBranch != 7 || svc.listRole != "admin" {
		t.Fatalf("unexpected list delegation: calls=%d branch=%d role=%q", svc.listCalls, svc.listBranch, svc.listRole)
	}
}

func TestDownloadPdfRejectsInvalidID(t *testing.T) {
	svc := &iomServiceMock{}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom/abc/pdf", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if svc.pdfCalls != 0 {
		t.Fatalf("expected pdf calls 0, got %d", svc.pdfCalls)
	}
}

func TestDownloadPdfMapsNotFoundTo404(t *testing.T) {
	svc := &iomServiceMock{pdfErr: gorm.ErrRecordNotFound}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom/31/pdf", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusNotFound {
		t.Fatalf("expected %d, got %d", fiber.StatusNotFound, resp.StatusCode)
	}
}

func TestDownloadPdfSetsHeadersAndBodyOnSuccess(t *testing.T) {
	svc := &iomServiceMock{pdfBytes: []byte("pdf-bytes"), pdfName: "IOM-777"}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom/77/pdf", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
	}
	if got := resp.Header.Get("Content-Type"); got != "application/pdf" {
		t.Fatalf("unexpected content type: %q", got)
	}
	if got := resp.Header.Get("Content-Disposition"); got != "attachment; filename=MEMORANDUM-IOM-777.pdf" {
		t.Fatalf("unexpected content disposition: %q", got)
	}
	if svc.pdfID != 77 || svc.pdfBranch != 7 || svc.pdfRole != "admin" {
		t.Fatalf("unexpected pdf delegation: id=%d branch=%d role=%q", svc.pdfID, svc.pdfBranch, svc.pdfRole)
	}
	body, readErr := io.ReadAll(resp.Body)
	if readErr != nil {
		t.Fatalf("failed reading body: %v", readErr)
	}
	if string(body) != "pdf-bytes" {
		t.Fatalf("unexpected body: %q", string(body))
	}
}

func TestExportExcelSetsHeaders(t *testing.T) {
	svc := &iomServiceMock{excelBytes: []byte("xlsx")}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodGet, "/iom/export/excel", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusOK {
		t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
	}
	if got := resp.Header.Get("Content-Type"); got != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
		t.Fatalf("unexpected content type: %q", got)
	}
	if got := resp.Header.Get("Content-Disposition"); got != `attachment; filename="IOM-General.xlsx"` {
		t.Fatalf("unexpected content disposition: %q", got)
	}
	if svc.excelCalls != 1 || svc.excelBranch != 7 || svc.excelRole != "admin" {
		t.Fatalf("unexpected excel delegation: calls=%d branch=%d role=%q", svc.excelCalls, svc.excelBranch, svc.excelRole)
	}
}

func TestDeleteMemorandumRejectsInvalidID(t *testing.T) {
	svc := &iomServiceMock{}
	h := NewHandler(svc)
	app := buildIOMHandlerTestApp(h)

	req := httptest.NewRequest(http.MethodDelete, "/iom/x", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if resp.StatusCode != fiber.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if svc.deleteCalls != 0 {
		t.Fatalf("expected delete calls 0, got %d", svc.deleteCalls)
	}
}

func TestDeleteMemorandumDelegatesScopeAndMapsError(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		svc := &iomServiceMock{}
		h := NewHandler(svc)
		app := buildIOMHandlerTestApp(h)

		req := httptest.NewRequest(http.MethodDelete, "/iom/99", nil)
		resp, err := app.Test(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		if resp.StatusCode != fiber.StatusOK {
			t.Fatalf("expected %d, got %d", fiber.StatusOK, resp.StatusCode)
		}
		if svc.deleteID != 99 || svc.deleteBranch != 7 || svc.deleteRole != "admin" {
			t.Fatalf("unexpected delete delegation: id=%d branch=%d role=%q", svc.deleteID, svc.deleteBranch, svc.deleteRole)
		}
	})

	t.Run("internal-error", func(t *testing.T) {
		svc := &iomServiceMock{deleteErr: errors.New("delete failed")}
		h := NewHandler(svc)
		app := buildIOMHandlerTestApp(h)

		req := httptest.NewRequest(http.MethodDelete, "/iom/99", nil)
		resp, err := app.Test(req)
		if err != nil {
			t.Fatalf("request failed: %v", err)
		}
		if resp.StatusCode != fiber.StatusInternalServerError {
			t.Fatalf("expected %d, got %d", fiber.StatusInternalServerError, resp.StatusCode)
		}
	})
}
