package controller

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

	"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
	approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
	service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
	customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
	approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"

	"github.com/gofiber/fiber/v2"
	"github.com/xuri/excelize/v2"
)

type deliveryOrdersServiceStub struct {
	getAllCalls []validation.DeliveryOrderQuery
}

var _ service.DeliveryOrdersService = (*deliveryOrdersServiceStub)(nil)

func (s *deliveryOrdersServiceStub) GetAll(_ *fiber.Ctx, params *validation.DeliveryOrderQuery) ([]dto.MarketingListDTO, int64, error) {
	callCopy := *params
	callCopy.ProductIDs = append([]uint(nil), params.ProductIDs...)
	s.getAllCalls = append(s.getAllCalls, callCopy)

	switch params.Page {
	case 1:
		return []dto.MarketingListDTO{
			buildMarketingListForControllerTest("SO-00001"),
			buildMarketingListForControllerTest("SO-00002"),
		}, 3, nil
	case 2:
		return []dto.MarketingListDTO{
			buildMarketingListForControllerTest("SO-00003"),
		}, 3, nil
	default:
		return []dto.MarketingListDTO{}, 3, nil
	}
}

func (s *deliveryOrdersServiceStub) GetOne(_ *fiber.Ctx, _ uint) (*dto.MarketingDetailDTO, error) {
	return nil, nil
}

func (s *deliveryOrdersServiceStub) CreateOne(_ *fiber.Ctx, _ *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error) {
	return nil, nil
}

func (s *deliveryOrdersServiceStub) UpdateOne(_ *fiber.Ctx, _ *validation.DeliveryOrderUpdate, _ uint) (*dto.MarketingDetailDTO, error) {
	return nil, nil
}

func (s *deliveryOrdersServiceStub) BulkApproveToStatus(_ *fiber.Ctx, _ *validation.BulkApprovalRequest, _ approvalutils.ApprovalStep) ([]dto.MarketingDetailDTO, error) {
	return nil, nil
}

func (s *deliveryOrdersServiceStub) GetProgressRows(_ *fiber.Ctx, _ *exportprogress.Query) ([]exportprogress.Row, error) {
	return nil, nil
}

func TestDeliveryOrdersControllerGetAllExportAllIgnoresRequestLimit(t *testing.T) {
	app := fiber.New()
	stub := &deliveryOrdersServiceStub{}
	ctrl := NewDeliveryOrdersController(stub)
	app.Get("/marketing", ctrl.GetAll)

	req := httptest.NewRequest(
		http.MethodGet,
		"/marketing?export=excel&type=all&page=9&limit=1&search=delivery&status=delivery_order&product_ids=1,2&customer_id=7&marketing_id=99",
		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 !strings.Contains(contentType, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
		t.Fatalf("unexpected content-type: %s", contentType)
	}

	disposition := resp.Header.Get("Content-Disposition")
	if !strings.Contains(disposition, "marketings_all_") {
		t.Fatalf("unexpected content-disposition: %s", disposition)
	}

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

	firstCall := stub.getAllCalls[0]
	secondCall := stub.getAllCalls[1]

	if firstCall.Page != 1 || secondCall.Page != 2 {
		t.Fatalf("expected internal paging to use pages 1 and 2, got %d and %d", firstCall.Page, secondCall.Page)
	}
	if firstCall.Limit != marketingExcelExportFetchLimit || secondCall.Limit != marketingExcelExportFetchLimit {
		t.Fatalf("expected internal limit %d, got %d and %d", marketingExcelExportFetchLimit, firstCall.Limit, secondCall.Limit)
	}
	if firstCall.Status != "delivery order" {
		t.Fatalf("expected status to normalize underscore to space, got %q", firstCall.Status)
	}
	if firstCall.Search != "delivery" {
		t.Fatalf("expected search to be forwarded, got %q", firstCall.Search)
	}
	if !reflect.DeepEqual(firstCall.ProductIDs, []uint{1, 2}) {
		t.Fatalf("unexpected product_ids: %+v", firstCall.ProductIDs)
	}
	if firstCall.CustomerId != 7 || firstCall.MarketingId != 99 {
		t.Fatalf("expected customer_id=7 and marketing_id=99, got customer_id=%d marketing_id=%d", firstCall.CustomerId, firstCall.MarketingId)
	}

	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()

	if got, _ := file.GetCellValue(marketingExportSheetName, "A1"); got != "No. Order" {
		t.Fatalf("expected A1 header to be No. Order, got %q", got)
	}
	if got, _ := file.GetCellValue(marketingExportSheetName, "A2"); got != "SO-00001" {
		t.Fatalf("expected first row order number SO-00001, got %q", got)
	}
}

func TestDeliveryOrdersControllerGetAllKeepsPaginationValidationForNonExportAll(t *testing.T) {
	app := fiber.New()
	stub := &deliveryOrdersServiceStub{}
	ctrl := NewDeliveryOrdersController(stub)
	app.Get("/marketing", ctrl.GetAll)

	req := httptest.NewRequest(http.MethodGet, "/marketing?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 buildMarketingListForControllerTest(orderNumber string) dto.MarketingListDTO {
	return dto.MarketingListDTO{
		MarketingRelationDTO: dto.MarketingRelationDTO{
			SoNumber: orderNumber,
			SoDate:   time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC),
			Notes:    "tes",
		},
		Customer: customerDTO.CustomerRelationDTO{
			Name: "AJAT",
		},
		SalesOrder: []dto.DeliveryMarketingProductDTO{
			{TotalPrice: 5206200000},
		},
		LatestApproval: approvalDTO.ApprovalRelationDTO{
			StepName: "Pengajuan",
		},
	}
}
