package controller

import (
	"bytes"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/xuri/excelize/v2"
	"gorm.io/gorm"

	approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
	kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
	locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
	nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto"
	supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
	service "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations"
)

type repportServiceStub struct {
	service.RepportService
	getExpenseCalls []validation.ExpenseQuery
}

func (s *repportServiceStub) GetExpense(_ *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error) {
	callCopy := *params
	callCopy.AllowedAreaIDs = append([]int64(nil), params.AllowedAreaIDs...)
	callCopy.AllowedLocationIDs = append([]int64(nil), params.AllowedLocationIDs...)
	s.getExpenseCalls = append(s.getExpenseCalls, callCopy)

	switch params.Page {
	case 1:
		return []dto.RepportExpenseListDTO{
			buildExpenseListForControllerTest("REF-00001", "TRANSPORT 2"),
			buildExpenseListForControllerTest("REF-00002", "TRANSPORT"),
		}, 3, nil
	case 2:
		return []dto.RepportExpenseListDTO{
			buildExpenseListForControllerTest("REF-00003", "TRANSPORT"),
		}, 3, nil
	default:
		return []dto.RepportExpenseListDTO{}, 3, nil
	}
}

func (s *repportServiceStub) DB() *gorm.DB {
	return nil
}

func TestRepportControllerGetExpenseExportAllIgnoresRequestLimit(t *testing.T) {
	app := fiber.New()
	stub := &repportServiceStub{}
	ctrl := NewRepportController(stub)
	app.Get("/reports/expense", ctrl.GetExpense)

	req := httptest.NewRequest(
		http.MethodGet,
		"/reports/expense?export=excel&type=all&page=9&limit=1&search=operasional&category=BOP&supplier_id=7&kandang_id=4&project_flock_kandang_id=2&nonstock_id=5&area_id=3&location_id=9&realization_date=2026-04-22",
		nil,
	)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected app.Test error: %v", err)
	}
	defer resp.Body.Close()

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

	contentType := resp.Header.Get("Content-Type")
	if contentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
		t.Fatalf("unexpected content-type: %s", contentType)
	}

	disposition := resp.Header.Get("Content-Disposition")
	if !bytes.Contains([]byte(disposition), []byte("reports_expense_all_")) {
		t.Fatalf("unexpected content-disposition: %s", disposition)
	}

	if len(stub.getExpenseCalls) != 2 {
		t.Fatalf("expected 2 GetExpense calls, got %d", len(stub.getExpenseCalls))
	}

	firstCall := stub.getExpenseCalls[0]
	secondCall := stub.getExpenseCalls[1]
	if firstCall.Page != 1 || secondCall.Page != 2 {
		t.Fatalf("expected internal pages 1 and 2, got %d and %d", firstCall.Page, secondCall.Page)
	}
	if firstCall.Limit != expenseReportExcelExportFetchLimit || secondCall.Limit != expenseReportExcelExportFetchLimit {
		t.Fatalf("expected internal limit %d, got %d and %d", expenseReportExcelExportFetchLimit, firstCall.Limit, secondCall.Limit)
	}

	if firstCall.Search != "operasional" ||
		firstCall.Category != "BOP" ||
		firstCall.SupplierId != 7 ||
		firstCall.KandangId != 4 ||
		firstCall.ProjectFlockKandangId != 2 ||
		firstCall.NonstockId != 5 ||
		firstCall.AreaId != 3 ||
		firstCall.LocationId != 9 ||
		firstCall.RealizationDate != "2026-04-22" {
		t.Fatalf("unexpected forwarded filters: %+v", firstCall)
	}

	payload, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("failed to read excel payload: %v", err)
	}
	file, err := excelize.OpenReader(bytes.NewReader(payload))
	if err != nil {
		t.Fatalf("failed to parse excel payload: %v", err)
	}
	defer file.Close()

	sheetList := file.GetSheetList()
	expectedSheets := []string{"EKSPEDISI ADE", "EKSPEDISI LTI"}
	if !reflect.DeepEqual(sheetList, expectedSheets) {
		t.Fatalf("unexpected sheet list: got %v, expected %v", sheetList, expectedSheets)
	}

	if got, _ := file.GetCellValue("EKSPEDISI ADE", "A1"); got != "No" {
		t.Fatalf("expected EKSPEDISI ADE A1 to be No, got %q", got)
	}
	if got, _ := file.GetCellValue("EKSPEDISI ADE", "G2"); got != "TRANSPORT 2" {
		t.Fatalf("expected EKSPEDISI ADE G2 to be TRANSPORT 2, got %q", got)
	}
	if got, _ := file.GetCellValue("EKSPEDISI LTI", "G2"); got != "TRANSPORT" {
		t.Fatalf("expected EKSPEDISI LTI G2 to be TRANSPORT, got %q", got)
	}
	if got, _ := file.GetCellValue("EKSPEDISI LTI", "A4"); got != "Total" {
		t.Fatalf("expected EKSPEDISI LTI A4 to be Total, got %q", got)
	}
}

func TestRepportControllerGetExpenseKeepsPaginationValidationForNonExportAll(t *testing.T) {
	app := fiber.New()
	stub := &repportServiceStub{}
	ctrl := NewRepportController(stub)
	app.Get("/reports/expense", ctrl.GetExpense)

	req := httptest.NewRequest(http.MethodGet, "/reports/expense?page=1&limit=0", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("unexpected app.Test error: %v", err)
	}
	defer resp.Body.Close()

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

func buildExpenseListForControllerTest(reference, product string) dto.RepportExpenseListDTO {
	realizationDate := time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC)
	return dto.RepportExpenseListDTO{
		RepportExpenseBaseDTO: dto.RepportExpenseBaseDTO{
			ReferenceNumber: reference,
			PoNumber:        "PO-001",
			Category:        "BOP",
			Notes:           "catatan expense",
			TransactionDate: time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC),
			RealizationDate: &realizationDate,
			Supplier: &supplierDTO.SupplierRelationDTO{
				Name: "Supplier A",
			},
		},
		Kandang: &kandangDTO.KandangRelationDTO{
			Name: "Kandang A",
			Location: &locationDTO.LocationRelationDTO{
				Name: "Darawati",
			},
		},
		Pengajuan: dto.RepportExpensePengajuanDTO{
			Qty:   1,
			Price: 50000,
			Notes: "catatan pengajuan",
			Nonstock: &nonstockDTO.NonstockRelationDTO{
				Name: product,
			},
		},
		Realisasi: dto.RepportExpenseRealisasiDTO{
			Qty:   1,
			Price: 50000,
			Notes: "catatan realisasi",
			Nonstock: &nonstockDTO.NonstockRelationDTO{
				Name: product,
			},
		},
		TotalPengajuan: 50000,
		TotalRealisasi: 50000,
		LatestApproval: &approvalDTO.ApprovalRelationDTO{
			StepName: "Realisasi",
		},
	}
}
