package controller

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
	m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
	"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"
	"gitlab.com/mbugroup/lti-api.git/internal/response"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"

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

type DeliveryOrdersController struct {
	DeliveryOrdersService service.DeliveryOrdersService
}

const marketingExcelExportFetchLimit = 100

func NewDeliveryOrdersController(deliveryOrdersService service.DeliveryOrdersService) *DeliveryOrdersController {
	return &DeliveryOrdersController{
		DeliveryOrdersService: deliveryOrdersService,
	}
}

func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error {
	if exportprogress.IsProgressExportRequest(c) {
		query, err := exportprogress.ParseQuery(c)
		if err != nil {
			return err
		}
		rows, err := u.DeliveryOrdersService.GetProgressRows(c, query)
		if err != nil {
			return err
		}
		content, err := exportprogress.BuildWorkbook("Marketings", query, rows)
		if err != nil {
			return fiber.NewError(fiber.StatusInternalServerError, "failed to generate progress excel file")
		}
		filename := fmt.Sprintf("marketings_progress_%s.xlsx", time.Now().Format("20060102_150405"))
		c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
		c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
		return c.Status(fiber.StatusOK).Send(content)
	}

	productIDs, err := parseUintListParam(c.Query("product_ids", ""))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid product_ids")
	}

	query := &validation.DeliveryOrderQuery{
		Page:                  c.QueryInt("page", 1),
		Limit:                 c.QueryInt("limit", 10),
		Search:                strings.TrimSpace(c.Query("search", "")),
		ProductIDs:            productIDs,
		Status:                strings.ReplaceAll(strings.TrimSpace(c.Query("status", "")), "_", " "),
		CustomerId:            uint(c.QueryInt("customer_id", 0)),
		MarketingId:           uint(c.QueryInt("marketing_id", 0)),
		ProjectFlockID:        uint(c.QueryInt("project_flock_id", 0)),
		ProjectFlockKandangID: uint(c.QueryInt("project_flock_kandang_id", 0)),
	}

	if isAllExcelExportRequest(c) {
		allResults, err := u.getAllMarketingRowsForExcel(c, query)
		if err != nil {
			return err
		}
		return exportMarketingListExcel(c, allResults)
	}

	if query.Page < 1 || query.Limit < 1 {
		return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
	}

	result, totalResults, err := u.DeliveryOrdersService.GetAll(c, query)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.MarketingListDTO]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get all deliveryOrderss successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
			},
			Data: result,
		})
}

func (u *DeliveryOrdersController) getAllMarketingRowsForExcel(c *fiber.Ctx, baseQuery *validation.DeliveryOrderQuery) ([]dto.MarketingListDTO, error) {
	query := *baseQuery
	query.Page = 1
	query.Limit = marketingExcelExportFetchLimit

	results := make([]dto.MarketingListDTO, 0)
	for {
		pageResults, total, err := u.DeliveryOrdersService.GetAll(c, &query)
		if err != nil {
			return nil, err
		}

		if len(pageResults) == 0 || total == 0 {
			break
		}

		results = append(results, pageResults...)
		if int64(len(results)) >= total {
			break
		}

		query.Page++
	}

	return results, nil
}

func parseUintListParam(param string) ([]uint, error) {
	if param == "" {
		return nil, nil
	}

	parts := strings.Split(param, ",")
	ids := make([]uint, 0, len(parts))
	for _, part := range parts {
		trimmed := strings.TrimSpace(part)
		if trimmed == "" {
			return nil, strconv.ErrSyntax
		}

		parsed, err := strconv.ParseUint(trimmed, 10, 64)
		if err != nil {
			return nil, err
		}
		ids = append(ids, uint(parsed))
	}

	return ids, nil
}

func (u *DeliveryOrdersController) GetOne(c *fiber.Ctx) error {
	param := c.Params("id")

	id, err := strconv.Atoi(param)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
	}

	result, err := u.DeliveryOrdersService.GetOne(c, uint(id))
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get deliveryOrders successfully",
			Data:    *result,
		})
}

func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error {
	req := new(validation.DeliveryOrderCreate)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	result, err := u.DeliveryOrdersService.CreateOne(c, req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusCreated).
		JSON(response.Success{
			Code:    fiber.StatusCreated,
			Status:  "success",
			Message: "Create delivery products successfully",
			Data:    result,
		})
}

func (u *DeliveryOrdersController) UpdateOne(c *fiber.Ctx) error {
	req := new(validation.DeliveryOrderUpdate)
	param := c.Params("id")

	id, err := strconv.Atoi(param)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
	}

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	result, err := u.DeliveryOrdersService.UpdateOne(c, req, uint(id))
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Update deliveryOrders successfully",
			Data:    result,
		})
}

func (u *DeliveryOrdersController) BulkApproveToStatus(c *fiber.Ctx) error {
	req := new(validation.BulkApprovalRequest)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	targetStep, err := req.ResolveTarget()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	if req.RequiresDate(targetStep) && strings.TrimSpace(req.Date) == "" {
		return fiber.NewError(fiber.StatusBadRequest, "date is required for DELIVERY bulk approval")
	}

	if err := ensureMarketingBulkApprovalPermission(c, targetStep); err != nil {
		return err
	}

	results, err := u.DeliveryOrdersService.BulkApproveToStatus(c, req, targetStep)
	if err != nil {
		return err
	}

	var (
		data    interface{}
		message = "Bulk approve marketing successfully"
	)
	if len(results) == 1 {
		data = results[0]
	} else {
		message = "Bulk approve marketings successfully"
		data = results
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: message,
			Data:    data,
		})
}

func ensureMarketingBulkApprovalPermission(c *fiber.Ctx, targetStep approvalutils.ApprovalStep) error {
	requiredPerms := []string{m.P_SalesOrderApproval}

	if targetStep == utils.MarketingDeliveryOrder {
		requiredPerms = append(requiredPerms, m.P_DeliveryUpdateOne)
	}

	for _, perm := range requiredPerms {
		if !m.HasPermission(c, perm) {
			return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
		}
	}

	return nil
}
