package controller

import (
	"encoding/json"
	"fmt"
	"math"
	"mime/multipart"
	"strconv"
	"strings"
	"time"

	"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/dto"
	service "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/services"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations"
	"gitlab.com/mbugroup/lti-api.git/internal/response"

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

const maxPurchaseUploadBytes = 5 * 1024 * 1024

type PurchaseController struct {
	service service.PurchaseService
}

const purchaseExcelExportFetchLimit = 100

func NewPurchaseController(s service.PurchaseService) *PurchaseController {
	return &PurchaseController{service: s}
}

func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
	if exportprogress.IsProgressExportRequest(c) {
		query, err := exportprogress.ParseQuery(c)
		if err != nil {
			return err
		}
		rows, err := ctrl.service.GetProgressRows(c, query)
		if err != nil {
			return err
		}
		content, err := exportprogress.BuildWorkbook("Purchases", query, rows)
		if err != nil {
			return fiber.NewError(fiber.StatusInternalServerError, "failed to generate progress excel file")
		}
		filename := fmt.Sprintf("purchases_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)
	}

	query := buildPurchaseQuery(c)

	if isAllPurchaseExcelExportRequest(c) {
		results, err := ctrl.getAllPurchasesForExcel(c, query)
		if err != nil {
			return err
		}
		return exportPurchaseListExcel(c, results)
	}

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

	results, total, err := ctrl.service.GetAll(c, query)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.PurchaseListDTO]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Purchase fetched successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(total) / float64(query.Limit))),
				TotalResults: total,
			},
			Data: dto.ToPurchaseListDTOs(results),
		})
}

func buildPurchaseQuery(c *fiber.Ctx) *validation.Query {
	return &validation.Query{
		Page:                  c.QueryInt("page", 1),
		Limit:                 c.QueryInt("limit", 10),
		Search:                strings.TrimSpace(c.Query("search")),
		ApprovalStatus:        strings.TrimSpace(c.Query("approval_status")),
		PoDate:                strings.TrimSpace(c.Query("po_date")),
		PoDateFrom:            strings.TrimSpace(c.Query("po_date_from")),
		PoDateTo:              strings.TrimSpace(c.Query("po_date_to")),
		CreatedFrom:           strings.TrimSpace(c.Query("created_from")),
		CreatedTo:             strings.TrimSpace(c.Query("created_to")),
		SupplierID:            uint(c.QueryInt("supplier_id", 0)),
		AreaID:                uint(c.QueryInt("area_id", 0)),
		LocationID:            uint(c.QueryInt("location_id", 0)),
		ProjectFlockID:        uint(c.QueryInt("project_flock_id", 0)),
		ProjectFlockKandangID: uint(c.QueryInt("project_flock_kandang_id", 0)),
		ProductCategoryID:     strings.TrimSpace(c.Query("product_category_id")),
	}
}

func (ctrl *PurchaseController) getAllPurchasesForExcel(c *fiber.Ctx, baseQuery *validation.Query) ([]entity.Purchase, error) {
	query := *baseQuery
	query.Page = 1
	query.Limit = purchaseExcelExportFetchLimit

	results := make([]entity.Purchase, 0)
	for {
		pageResults, total, err := ctrl.service.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 (ctrl *PurchaseController) GetOne(c *fiber.Ctx) error {
	param := c.Params("id")

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

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

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Purchase fetched successfully",
			Data:    dto.ToPurchaseDetailDTO(*result),
		})
}

func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error {
	req := new(validation.CreatePurchaseRequest)

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

	return c.Status(fiber.StatusCreated).
		JSON(response.Success{
			Code:    fiber.StatusCreated,
			Status:  "success",
			Message: "Purchase created successfully",
			Data:    dto.ToPurchaseDetailDTO(*result),
		})
}

func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
	param := c.Params("id")
	id, err := strconv.Atoi(param)
	if err != nil || id == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
	}

	req := new(validation.ApproveStaffPurchaseRequest)
	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %v", err))
	}

	result, err := ctrl.service.ApproveStaffPurchase(c, uint(id), req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Staff purchase approval recorded successfully",
			Data:    dto.ToPurchaseDetailDTO(*result),
		})
}

func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
	param := c.Params("id")
	id, err := strconv.Atoi(param)
	if err != nil || id == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
	}

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

	result, err := ctrl.service.ApproveManagerPurchase(c, uint(id), req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Manager purchase approval recorded successfully",
			Data:    dto.ToPurchaseDetailDTO(*result),
		})
}

func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
	param := c.Params("id")
	id, err := strconv.Atoi(param)
	if err != nil || id == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
	}

	req := new(validation.ReceivePurchaseRequest)
	form, err := c.MultipartForm()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
	}
	req.Action = c.FormValue("action")
	if notes := strings.TrimSpace(c.FormValue("notes")); notes != "" {
		req.Notes = &notes
	}

	itemsJSON := c.FormValue("items")
	if strings.TrimSpace(itemsJSON) != "" {
		if err := json.Unmarshal([]byte(itemsJSON), &req.Items); err != nil {
			var singleItem validation.ReceivePurchaseItemRequest
			if err := json.Unmarshal([]byte(itemsJSON), &singleItem); err != nil {
				return fiber.NewError(fiber.StatusBadRequest, "Invalid items JSON")
			}
			req.Items = []validation.ReceivePurchaseItemRequest{singleItem}
		}
	}
	req.TravelDocuments = form.File["travel_documents"]
	if len(req.TravelDocuments) == 0 {
		req.TravelDocuments = form.File["documents"]
	}
	if err := validatePurchaseDocumentSizes(req.TravelDocuments); err != nil {
		return err
	}
	result, err := ctrl.service.ReceiveProducts(c, uint(id), req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Purchase receiving recorded successfully",
			Data:    dto.ToPurchaseDetailDTO(*result),
		})
}

func validatePurchaseDocumentSizes(files []*multipart.FileHeader) error {
	for _, file := range files {
		if file != nil && file.Size > maxPurchaseUploadBytes {
			return fiber.NewError(fiber.StatusRequestEntityTooLarge, "Document size must be <= 5MB")
		}
	}
	return nil
}

func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
	param := c.Params("id")
	id, err := strconv.Atoi(param)
	if err != nil || id == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
	}

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

	result, err := ctrl.service.DeleteItems(c, uint(id), req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Purchase items deleted successfully",
			Data:    dto.ToPurchaseDetailDTO(*result),
		})
}

func (ctrl *PurchaseController) DeletePurchase(c *fiber.Ctx) error {
	param := c.Params("id")
	id, err := strconv.Atoi(param)
	if err != nil || id == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
	}

	if err := ctrl.service.DeletePurchase(c, uint(id)); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Success{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Purchase deleted successfully",
			Data:    nil,
		})
}
