package controller

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

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

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

// === Marketing Report Response ===

type MarketingReportResponse struct {
	Code    int                           `json:"code"`
	Status  string                        `json:"status"`
	Message string                        `json:"message"`
	Meta    response.Meta                 `json:"meta"`
	Data    []dto.RepportMarketingItemDTO `json:"data"`
	Total   *dto.Summary                  `json:"total,omitempty"`
}

type RepportController struct {
	RepportService service.RepportService
}

const expenseReportExcelExportFetchLimit = 100

func NewRepportController(repportService service.RepportService) *RepportController {
	return &RepportController{
		RepportService: repportService,
	}
}

func (c *RepportController) GetExpense(ctx *fiber.Ctx) error {
	query := &validation.ExpenseQuery{
		Page:                  ctx.QueryInt("page", 1),
		Limit:                 ctx.QueryInt("limit", 10),
		Search:                ctx.Query("search", ""),
		Category:              ctx.Query("category", ""),
		SupplierId:            int64(ctx.QueryInt("supplier_id", 0)),
		KandangId:             int64(ctx.QueryInt("kandang_id", 0)),
		ProjectFlockKandangId: int64(ctx.QueryInt("project_flock_kandang_id", 0)),
		NonstockId:            int64(ctx.QueryInt("nonstock_id", 0)),
		AreaId:                int64(ctx.QueryInt("area_id", 0)),
		LocationId:            int64(ctx.QueryInt("location_id", 0)),
		RealizationDate:       ctx.Query("realization_date", ""),
	}

	locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	if locationScope.Restrict {
		query.AllowedLocationIDs = toInt64Slice(locationScope.IDs)
	}
	if areaScope.Restrict {
		query.AllowedAreaIDs = toInt64Slice(areaScope.IDs)
	}

	if isAllExpenseExcelExportRequest(ctx) {
		allResults, err := c.getAllExpenseRowsForExcel(ctx, query)
		if err != nil {
			return err
		}
		return exportExpenseReportListExcel(ctx, allResults)
	}

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

	result, totalResults, err := c.RepportService.GetExpense(ctx, query)
	if err != nil {
		return err
	}

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

func (c *RepportController) getAllExpenseRowsForExcel(ctx *fiber.Ctx, baseQuery *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, error) {
	query := *baseQuery
	query.Page = 1
	query.Limit = expenseReportExcelExportFetchLimit

	results := make([]dto.RepportExpenseListDTO, 0)
	for {
		pageResults, total, err := c.RepportService.GetExpense(ctx, &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 (c *RepportController) GetExpenseDepreciation(ctx *fiber.Ctx) error {
	rows, meta, err := c.RepportService.GetExpenseDepreciation(ctx)
	if err != nil {
		return err
	}

	resp := struct {
		Code    int                             `json:"code"`
		Status  string                          `json:"status"`
		Message string                          `json:"message"`
		Meta    dto.ExpenseDepreciationMetaDTO  `json:"meta"`
		Data    []dto.ExpenseDepreciationRowDTO `json:"data"`
	}{
		Code:    fiber.StatusOK,
		Status:  "success",
		Message: "Get expense depreciation report successfully",
		Meta:    *meta,
		Data:    rows,
	}

	return ctx.Status(fiber.StatusOK).JSON(resp)
}

func (c *RepportController) GetExpenseDepreciationManualInputs(ctx *fiber.Ctx) error {
	rows, meta, err := c.RepportService.GetExpenseDepreciationManualInputs(ctx)
	if err != nil {
		return err
	}

	resp := struct {
		Code    int                                        `json:"code"`
		Status  string                                     `json:"status"`
		Message string                                     `json:"message"`
		Meta    dto.ExpenseDepreciationMetaDTO             `json:"meta"`
		Data    []dto.ExpenseDepreciationManualInputRowDTO `json:"data"`
	}{
		Code:    fiber.StatusOK,
		Status:  "success",
		Message: "Get expense depreciation manual inputs successfully",
		Meta:    *meta,
		Data:    rows,
	}

	return ctx.Status(fiber.StatusOK).JSON(resp)
}

func (c *RepportController) UpsertExpenseDepreciationManualInput(ctx *fiber.Ctx) error {
	req := new(validation.ExpenseDepreciationManualInputUpsert)
	if err := ctx.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if err := m.EnsureProjectFlockAccess(ctx, c.RepportService.DB(), req.ProjectFlockID); err != nil {
		return err
	}

	result, err := c.RepportService.UpsertExpenseDepreciationManualInput(ctx, req)
	if err != nil {
		return err
	}

	return ctx.Status(fiber.StatusOK).JSON(response.Success{
		Code:    fiber.StatusOK,
		Status:  "success",
		Message: "Upsert expense depreciation manual input successfully",
		Data:    result,
	})
}

func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
	query := &validation.MarketingQuery{
		Page:          ctx.QueryInt("page", 1),
		Limit:         ctx.QueryInt("limit", 10),
		Search:        ctx.Query("search", ""),
		CustomerId:    int64(ctx.QueryInt("customer_id", 0)),
		ProductId:     int64(ctx.QueryInt("product_id", 0)),
		WarehouseId:   int64(ctx.QueryInt("warehouse_id", 0)),
		SalesPersonId: int64(ctx.QueryInt("sales_person_id", 0)),
		AreaId:        int64(ctx.QueryInt("area_id", 0)),
		LocationId:    int64(ctx.QueryInt("location_id", 0)),
		MarketingType: ctx.Query("marketing_type", ""),
		FilterBy:      ctx.Query("filter_by", ""),
		StartDate:     ctx.Query("start_date", ""),
		EndDate:       ctx.Query("end_date", ""),
		SortBy:        ctx.Query("sort_by", ""),
		SortOrder:     ctx.Query("sort_order", ""),
	}

	locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	if locationScope.Restrict {
		allowed := toInt64Slice(locationScope.IDs)
		if len(allowed) == 0 {
			allowed = []int64{-1}
		}
		query.AllowedLocationIDs = allowed
	}
	if areaScope.Restrict {
		allowed := toInt64Slice(areaScope.IDs)
		if len(allowed) == 0 {
			allowed = []int64{-1}
		}
		query.AllowedAreaIDs = allowed
	}

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

	result, totalResults, err := c.RepportService.GetMarketing(ctx, query)
	if err != nil {
		return err
	}
	total := dto.ToSummaryFromDTOItems(result)

	return ctx.Status(fiber.StatusOK).
		JSON(MarketingReportResponse{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get marketing report successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
			},
			Data:  result,
			Total: total,
		})
}

func (c *RepportController) GetPurchaseSupplier(ctx *fiber.Ctx) error {
	areaIDs, err := parseCommaSeparatedInt64sWithField(ctx.Query("area_id", ""), "area_id")
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}
	supplierIDs, err := parseCommaSeparatedInt64sWithField(ctx.Query("supplier_id", ""), "supplier_id")
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}
	productIDs, err := parseCommaSeparatedInt64sWithField(ctx.Query("product_id", ""), "product_id")
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}
	productCategoryIDs, err := parseCommaSeparatedInt64sWithField(ctx.Query("product_category_id", ""), "product_category_id")
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	query := &validation.PurchaseSupplierQuery{
		Page:               ctx.QueryInt("page", 1),
		Limit:              ctx.QueryInt("limit", 10),
		AreaIDs:            areaIDs,
		SupplierIDs:        supplierIDs,
		ProductIDs:         productIDs,
		ProductCategoryIDs: productCategoryIDs,
		StartDate:          ctx.Query("start_date", ""),
		EndDate:            ctx.Query("end_date", ""),
		SortBy:             ctx.Query("sort_by", ""),
		FilterBy:           ctx.Query("filter_by", ""),
	}

	areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	if areaScope.Restrict {
		query.AllowedAreaIDs = toInt64Slice(areaScope.IDs)
	}

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

	result, totalResults, err := c.RepportService.GetPurchaseSupplier(ctx, query)
	if err != nil {
		return err
	}

	filters := map[string]interface{}{
		"area_id":             query.AreaIDs,
		"supplier_id":         query.SupplierIDs,
		"product_id":          query.ProductIDs,
		"product_category_id": query.ProductCategoryIDs,
		"start_date":          query.StartDate,
		"end_date":            query.EndDate,
		"sort_by":             query.SortBy,
		"filter_by":           query.FilterBy,
	}

	return ctx.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.PurchaseSupplierDTO]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get supplier purchase recap successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
				Filters:      filters,
			},
			Data: result,
		})
}

func (c *RepportController) GetDebtSupplier(ctx *fiber.Ctx) error {
	supplierIDs, err := parseCommaSeparatedInt64s(ctx.Query("supplier_ids", ""))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	query := &validation.DebtSupplierQuery{
		Page:        ctx.QueryInt("page", 1),
		Limit:       ctx.QueryInt("limit", 10),
		SupplierIDs: supplierIDs,
		StartDate:   ctx.Query("start_date", ""),
		EndDate:     ctx.Query("end_date", ""),
		FilterBy:    ctx.Query("filter_by", ""),
		SortOrder:   ctx.Query("sort_order", ""),
	}

	locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB())
	if err != nil {
		return err
	}
	if locationScope.Restrict {
		query.AllowedLocationIDs = toInt64Slice(locationScope.IDs)
	}
	if areaScope.Restrict {
		query.AllowedAreaIDs = toInt64Slice(areaScope.IDs)
	}

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

	result, totalResults, err := c.RepportService.GetDebtSupplier(ctx, query)
	if err != nil {
		return err
	}

	supplierIDs = query.SupplierIDs
	if supplierIDs == nil {
		supplierIDs = []int64{}
	}

	filters := map[string]interface{}{
		"start_date":   query.StartDate,
		"end_date":     query.EndDate,
		"supplier_ids": supplierIDs,
		"filter_by":    query.FilterBy,
	}

	return ctx.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.DebtSupplierDTO]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get supplier debt recap successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
				Filters:      filters,
			},
			Data: result,
		})
}

func (c *RepportController) GetHppPerKandang(ctx *fiber.Ctx) error {
	data, meta, err := c.RepportService.GetHppPerKandang(ctx)
	if err != nil {
		return err
	}

	resp := struct {
		Code    int                           `json:"code"`
		Status  string                        `json:"status"`
		Message string                        `json:"message"`
		Meta    dto.HppPerKandangMetaDTO      `json:"meta"`
		Data    dto.HppPerKandangResponseData `json:"data"`
	}{
		Code:    fiber.StatusOK,
		Status:  "success",
		Message: "Get HPP harian kandang layer successfully",
		Meta:    *meta,
		Data:    *data,
	}

	return ctx.Status(fiber.StatusOK).JSON(resp)
}

func (c *RepportController) GetCustomerPayment(ctx *fiber.Ctx) error {
	var customerIDs []uint
	if customerIDsStr := ctx.Query("customer_ids"); customerIDsStr != "" {
		ids := strings.Split(customerIDsStr, ",")
		for _, idStr := range ids {
			idStr = strings.TrimSpace(idStr)
			if idStr != "" {
				if id, err := strconv.ParseUint(idStr, 10, 32); err == nil {
					customerIDs = append(customerIDs, uint(id))
				}
			}
		}
	}

	query := &validation.CustomerPaymentQuery{
		Page:        ctx.QueryInt("page", 1),
		Limit:       ctx.QueryInt("limit", 10),
		CustomerIDs: customerIDs,
		FilterBy:    strings.ToUpper(ctx.Query("filter_by", "")),
		StartDate:   ctx.Query("start_date", ""),
		EndDate:     ctx.Query("end_date", ""),
	}

	// Validate pagination
	if len(customerIDs) == 0 && (query.Page < 1 || query.Limit < 1) {
		return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0 when customer_ids is not provided")
	}

	result, totalResults, err := c.RepportService.GetCustomerPayment(ctx, query)
	if err != nil {
		return err
	}

	// If single customer mode (only 1 customer ID), return without pagination
	if len(customerIDs) == 1 {
		return ctx.Status(fiber.StatusOK).
			JSON(response.Success{
				Code:    fiber.StatusOK,
				Status:  "success",
				Message: "Get customer payment report successfully",
				Data:    result,
			})
	}

	// Multiple customers mode with pagination
	return ctx.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.CustomerPaymentReportItem]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get customer payment report successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
			},
			Data: result,
		})
}

func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error {
	idParam := ctx.Params("idProjectFlockKandang")
	if idParam == "" {
		return fiber.NewError(fiber.StatusBadRequest, "idProjectFlockKandang is required")
	}

	projectFlockKandangID, err := strconv.ParseUint(idParam, 10, 64)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid idProjectFlockKandang")
	}

	query := &validation.ProductionResultQuery{
		Page:                  ctx.QueryInt("page", 1),
		Limit:                 ctx.QueryInt("limit", 10),
		ProjectFlockKandangID: uint(projectFlockKandangID),
	}

	if err := m.EnsureProjectFlockKandangAccess(ctx, c.RepportService.DB(), 0, query.ProjectFlockKandangID); err != nil {
		return err
	}

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

	data, totalResults, err := c.RepportService.GetProductionResult(ctx, query)
	if err != nil {
		return err
	}

	return ctx.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.ProductionResultDTO]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get Laporan Hasil Produksi successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
			},
			Data: data,
		})
}

func (c *RepportController) GetHppV2Breakdown(ctx *fiber.Ctx) error {
	query := &validation.HppV2BreakdownQuery{
		ProjectFlockKandangID: uint(ctx.QueryInt("project_flock_kandang_id", 0)),
		Period:                ctx.Query("period", ""),
	}

	if err := m.EnsureProjectFlockKandangAccess(ctx, c.RepportService.DB(), 0, query.ProjectFlockKandangID); err != nil {
		return err
	}

	data, err := c.RepportService.GetHppV2Breakdown(ctx, query)
	if err != nil {
		return err
	}

	return ctx.Status(fiber.StatusOK).JSON(response.Success{
		Code:    fiber.StatusOK,
		Status:  "success",
		Message: "Get HPP v2 breakdown successfully",
		Data:    data,
	})
}

func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
	return parseCommaSeparatedInt64sWithField(raw, "supplier_ids")
}

func parseCommaSeparatedInt64sWithField(raw, field string) ([]int64, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return []int64{}, nil
	}

	parts := strings.Split(raw, ",")
	result := make([]int64, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}

		id, err := strconv.ParseInt(part, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("%s must be comma separated integers", field)
		}
		result = append(result, id)
	}

	return result, nil
}

func toInt64Slice(ids []uint) []int64 {
	if len(ids) == 0 {
		return nil
	}
	out := make([]int64, 0, len(ids))
	for _, id := range ids {
		out = append(out, int64(id))
	}
	return out
}
