package sjr

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"system-altrak/internal/domain"
	"system-altrak/internal/dto"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"
)

type sjrServiceMock struct {
	createData *domain.ServiceAuthorization
	createErr  error
	listData   []domain.ServiceAuthorization
	listErr    error
	pdfData    []byte
	pdfRef     string
	pdfErr     error
	excelData  []byte
	excelErr   error
	deleteErr  error

	createCalls int
	listCalls   int
	pdfCalls    int
	excelCalls  int
	deleteCalls int

	lastCreateReq    *dto.ServiceJobRequestDTO
	lastCreateUserID uint
	lastCreateBranch uint

	lastListBranch uint
	lastListRole   string

	lastPDFID     uint
	lastPDFBranch uint
	lastPDFRole   string

	lastExcelBranch uint
	lastExcelRole   string
	lastExcelType   string

	lastDeleteID     uint
	lastDeleteBranch uint
	lastDeleteRole   string
}

func cloneServiceJobRequestDTO(req *dto.ServiceJobRequestDTO) *dto.ServiceJobRequestDTO {
	if req == nil {
		return nil
	}
	copy := *req
	return &copy
}

func (m *sjrServiceMock) CreateAuthorization(req *dto.ServiceJobRequestDTO, userID, branchID uint) (*domain.ServiceAuthorization, error) {
	m.createCalls++
	m.lastCreateReq = cloneServiceJobRequestDTO(req)
	m.lastCreateUserID = userID
	m.lastCreateBranch = branchID
	if m.createErr != nil {
		return nil, m.createErr
	}
	if m.createData != nil {
		return m.createData, nil
	}
	return &domain.ServiceAuthorization{Ref: "001/SJR-BJM/I/26"}, nil
}

func (m *sjrServiceMock) UpdateAuthorization(id uint, req *dto.ServiceJobRequestDTO, branchID uint, role string) (*domain.ServiceAuthorization, error) {
	return nil, nil
}

func (m *sjrServiceMock) GetAuthorizationByID(id uint, branchID uint, role string) (*domain.ServiceAuthorization, error) {
	return nil, nil
}

func (m *sjrServiceMock) ListAuthorizations(branchID uint, role string) ([]domain.ServiceAuthorization, error) {
	m.listCalls++
	m.lastListBranch = branchID
	m.lastListRole = role
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.listData, nil
}

func (m *sjrServiceMock) SynthesizePDF(id uint, branchID uint, role string) ([]byte, string, error) {
	m.pdfCalls++
	m.lastPDFID = id
	m.lastPDFBranch = branchID
	m.lastPDFRole = role
	if m.pdfErr != nil {
		return nil, "", m.pdfErr
	}
	if m.pdfData == nil {
		m.pdfData = []byte("pdf-bytes")
	}
	if m.pdfRef == "" {
		m.pdfRef = "001-SJR"
	}
	return m.pdfData, m.pdfRef, nil
}

func (m *sjrServiceMock) ExportExcel(branchID uint, role string, requestType string) ([]byte, error) {
	m.excelCalls++
	m.lastExcelBranch = branchID
	m.lastExcelRole = role
	m.lastExcelType = requestType
	if m.excelErr != nil {
		return nil, m.excelErr
	}
	if m.excelData == nil {
		m.excelData = []byte("xlsx-bytes")
	}
	return m.excelData, nil
}

func (m *sjrServiceMock) Delete(id uint, branchID uint, role string) error {
	m.deleteCalls++
	m.lastDeleteID = id
	m.lastDeleteBranch = branchID
	m.lastDeleteRole = role
	if m.deleteErr != nil {
		return m.deleteErr
	}
	return nil
}

func buildSJRHandlerTestApp(h *Handler, withLocals bool, userID uint, branchID uint, role string) *fiber.App {
	app := fiber.New()
	if withLocals {
		app.Use(func(c *fiber.Ctx) error {
			c.Locals("user_id", userID)
			c.Locals("current_branch_id", branchID)
			c.Locals("role", role)
			return c.Next()
		})
	}

	app.Post("/sjr", h.Create)
	app.Get("/sjr", h.List)
	app.Get("/sjr/:id/pdf", h.DownloadPDF)
	app.Get("/sjr/excel", h.DownloadExcel)
	app.Delete("/sjr/:id", h.Delete)
	return app
}

func decodeSJRResponseMap(t *testing.T, resp *http.Response) map[string]interface{} {
	t.Helper()
	var body map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	return body
}

func TestCreateReturnsSuccessAndDelegatesContext(t *testing.T) {
	svc := &sjrServiceMock{createData: &domain.ServiceAuthorization{Ref: "001/SJR-BJM/I/26", CustomerName: "PT Atlas"}}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 8, 3, "admin")

	payload := `{"request_type":"repair","customer_name":"PT Atlas","job_name":"Overhaul","component":"Injector","engine_brand":"CAT","engine_model":"C7","esn":"ESN-01"}`
	req := httptest.NewRequest(http.MethodPost, "/sjr", 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.lastCreateUserID != 8 || svc.lastCreateBranch != 3 {
		t.Fatalf("unexpected create args calls=%d user=%d branch=%d", svc.createCalls, svc.lastCreateUserID, svc.lastCreateBranch)
	}
	if svc.lastCreateReq == nil || svc.lastCreateReq.CustomerName != "PT Atlas" || svc.lastCreateReq.EngineModel != "C7" {
		t.Fatalf("unexpected create request payload: %+v", svc.lastCreateReq)
	}

	body := decodeSJRResponseMap(t, resp)
	if body["success"] != true {
		t.Fatalf("expected success=true, got=%v", body["success"])
	}
}

func TestCreateRejectsInvalidJSON(t *testing.T) {
	svc := &sjrServiceMock{}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 2, "manager")

	req := httptest.NewRequest(http.MethodPost, "/sjr", bytes.NewBufferString("{invalid-json"))
	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("did not expect create service call, got=%d", svc.createCalls)
	}
}

func TestCreateValidationFailureDoesNotCallService(t *testing.T) {
	svc := &sjrServiceMock{}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 2, 1, "staff")

	payload := `{"request_type":"repair","customer_name":"PT Atlas"}`
	req := httptest.NewRequest(http.MethodPost, "/sjr", 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.StatusBadRequest {
		t.Fatalf("expected %d, got %d", fiber.StatusBadRequest, resp.StatusCode)
	}
	if svc.createCalls != 0 {
		t.Fatalf("did not expect create service call on validation failure, got=%d", svc.createCalls)
	}
}

func TestListReturnsSuccessAndDelegatesContext(t *testing.T) {
	svc := &sjrServiceMock{listData: []domain.ServiceAuthorization{{Ref: "001", CustomerName: "PT A", BranchID: 9}}}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 3, 9, "manager")

	req := httptest.NewRequest(http.MethodGet, "/sjr", 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.lastListBranch != 9 || svc.lastListRole != "manager" {
		t.Fatalf("unexpected list args calls=%d branch=%d role=%q", svc.listCalls, svc.lastListBranch, svc.lastListRole)
	}
}

func TestListReturnsInternalErrorWhenServiceFails(t *testing.T) {
	svc := &sjrServiceMock{listErr: errors.New("db error")}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 2, 4, "admin")

	req := httptest.NewRequest(http.MethodGet, "/sjr", 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)
	}
}

func TestDownloadPDFRejectsInvalidID(t *testing.T) {
	svc := &sjrServiceMock{}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 2, "admin")

	req := httptest.NewRequest(http.MethodGet, "/sjr/invalid/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("did not expect pdf service call, got=%d", svc.pdfCalls)
	}
}

func TestDownloadPDFReturnsNotFoundOnRecordMissing(t *testing.T) {
	svc := &sjrServiceMock{pdfErr: gorm.ErrRecordNotFound}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 5, "user")

	req := httptest.NewRequest(http.MethodGet, "/sjr/12/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)
	}
	if svc.pdfCalls != 1 || svc.lastPDFID != 12 || svc.lastPDFBranch != 5 || svc.lastPDFRole != "user" {
		t.Fatalf("unexpected pdf args calls=%d id=%d branch=%d role=%q", svc.pdfCalls, svc.lastPDFID, svc.lastPDFBranch, svc.lastPDFRole)
	}
}

func TestDownloadPDFReturnsPDFWithHeadersOnSuccess(t *testing.T) {
	svc := &sjrServiceMock{pdfData: []byte("pdf-content"), pdfRef: "009-SJR"}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 7, "admin")

	req := httptest.NewRequest(http.MethodGet, "/sjr/9/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("expected Content-Type application/pdf, got %q", got)
	}
	if got := resp.Header.Get("Content-Disposition"); got != "attachment; filename=AUTH-009-SJR.pdf" {
		t.Fatalf("unexpected Content-Disposition: %q", got)
	}
}

func TestDownloadExcelRejectsInvalidTypeQuery(t *testing.T) {
	svc := &sjrServiceMock{}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 6, "manager")

	req := httptest.NewRequest(http.MethodGet, "/sjr/excel?type=wrong", 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.excelCalls != 0 {
		t.Fatalf("did not expect excel service call on invalid type, got=%d", svc.excelCalls)
	}
}

func TestDownloadExcelReturnsSuccessAndDelegatesContext(t *testing.T) {
	svc := &sjrServiceMock{excelData: []byte("xlsx")}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 6, "manager")

	req := httptest.NewRequest(http.MethodGet, "/sjr/excel?type=repair", 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.excelCalls != 1 || svc.lastExcelBranch != 6 || svc.lastExcelRole != "manager" || svc.lastExcelType != "repair" {
		t.Fatalf("unexpected excel args calls=%d branch=%d role=%q type=%q", svc.excelCalls, svc.lastExcelBranch, svc.lastExcelRole, svc.lastExcelType)
	}
	if got := resp.Header.Get("Content-Type"); got != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
		t.Fatalf("unexpected Content-Type: %q", got)
	}
}

func TestDeleteRejectsInvalidID(t *testing.T) {
	svc := &sjrServiceMock{}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 4, "admin")

	req := httptest.NewRequest(http.MethodDelete, "/sjr/not-a-number", 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("did not expect delete service call on invalid id, got=%d", svc.deleteCalls)
	}
}

func TestDeleteReturnsInternalErrorWhenServiceFails(t *testing.T) {
	svc := &sjrServiceMock{deleteErr: errors.New("delete failed")}
	h := NewHandler(svc)
	app := buildSJRHandlerTestApp(h, true, 1, 4, "admin")

	req := httptest.NewRequest(http.MethodDelete, "/sjr/20", 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)
	}
	if svc.deleteCalls != 1 || svc.lastDeleteID != 20 || svc.lastDeleteBranch != 4 || svc.lastDeleteRole != "admin" {
		t.Fatalf("unexpected delete args calls=%d id=%d branch=%d role=%q", svc.deleteCalls, svc.lastDeleteID, svc.lastDeleteBranch, svc.lastDeleteRole)
	}
}
