package controller

import (
	"encoding/json"
	"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/expenses/dto"
	service "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/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 ExpenseController struct {
	ExpenseService service.ExpenseService
}

const expenseExcelExportFetchLimit = 100

func NewExpenseController(expenseService service.ExpenseService) *ExpenseController {
	return &ExpenseController{
		ExpenseService: expenseService,
	}
}

func (u *ExpenseController) GetAll(c *fiber.Ctx) error {
	if exportprogress.IsProgressExportRequest(c) {
		query, err := exportprogress.ParseQuery(c)
		if err != nil {
			return err
		}
		rows, err := u.ExpenseService.GetProgressRows(c, query)
		if err != nil {
			return err
		}
		content, err := exportprogress.BuildWorkbook("Expenses", query, rows)
		if err != nil {
			return fiber.NewError(fiber.StatusInternalServerError, "failed to generate progress excel file")
		}
		filename := fmt.Sprintf("expenses_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 := &validation.Query{
		Page:                  c.QueryInt("page", 1),
		Limit:                 c.QueryInt("limit", 10),
		Search:                strings.TrimSpace(c.Query("search", "")),
		TransactionDate:       strings.TrimSpace(c.Query("transaction_date", "")),
		RealizationDate:       strings.TrimSpace(c.Query("realization_date", "")),
		LocationID:            uint64(c.QueryInt("location_id", 0)),
		VendorID:              uint64(c.QueryInt("vendor_id", 0)),
		Category:              strings.TrimSpace(c.Query("category", "")),
		ApprovalStatus:        strings.TrimSpace(c.Query("approval_status", "")),
		RealizationStatus:     strings.TrimSpace(c.Query("realization_status", "")),
		ProjectFlockID:        uint64(c.QueryInt("project_flock_id", 0)),
		ProjectFlockKandangID: uint64(c.QueryInt("project_flock_kandang_id", 0)),
	}

	if isAllExpenseExcelExportRequest(c) {
		allResults, err := u.getAllExpensesForExcel(c, query)
		if err != nil {
			return err
		}
		return exportExpenseListExcel(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.ExpenseService.GetAll(c, query)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[dto.ExpenseListDTO]{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get all expenses 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 *ExpenseController) getAllExpensesForExcel(c *fiber.Ctx, baseQuery *validation.Query) ([]dto.ExpenseListDTO, error) {
	query := *baseQuery
	query.Page = 1
	query.Limit = expenseExcelExportFetchLimit

	results := make([]dto.ExpenseListDTO, 0)
	for {
		pageResults, total, err := u.ExpenseService.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 (u *ExpenseController) 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.ExpenseService.GetOne(c, uint(id))
	if err != nil {
		return err
	}

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

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

	req.TransactionDate = c.FormValue("transaction_date")
	req.Category = c.FormValue("category")

	supplierID, err := strconv.ParseUint(c.FormValue("supplier_id"), 10, 64)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid supplier_id format")
	}
	req.SupplierID = supplierID

	locationID, err := strconv.ParseUint(c.FormValue("location_id"), 10, 64)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id format")
	}
	req.LocationID = locationID

	form, err := c.MultipartForm()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
	}
	req.Documents = form.File["documents"]

	expenseNonstocksJSON := c.FormValue("expense_nonstocks")
	if expenseNonstocksJSON != "" {

		if err := json.Unmarshal([]byte(expenseNonstocksJSON), &req.ExpenseNonstocks); err != nil {

			var singleExpenseNonstock validation.ExpenseNonstock
			if err := json.Unmarshal([]byte(expenseNonstocksJSON), &singleExpenseNonstock); err != nil {
				return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
			}

			req.ExpenseNonstocks = []validation.ExpenseNonstock{singleExpenseNonstock}
		}
	} else {
		return fiber.NewError(fiber.StatusBadRequest, "Field expense_nonstocks is required")
	}

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

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

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

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

	form, err := c.MultipartForm()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
	}

	req.Documents = form.File["documents"]

	transactionDate := c.FormValue("transaction_date")
	if transactionDate != "" {
		req.TransactionDate = &transactionDate
	}

	categoryVal := c.FormValue("category")
	if categoryVal != "" {
		req.Category = &categoryVal
	}

	supplierIDVal := c.FormValue("supplier_id")
	if supplierIDVal != "" {
		supplierID, err := strconv.ParseUint(supplierIDVal, 10, 64)
		if err != nil {
			return fiber.NewError(fiber.StatusBadRequest, "Invalid supplier_id format")
		}
		req.SupplierID = &supplierID
	}

	locationIDVal := c.FormValue("location_id")
	if locationIDVal != "" {
		locationID, err := strconv.ParseUint(locationIDVal, 10, 64)
		if err != nil {
			return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id format")
		}
		req.LocationID = &locationID
	}

	expenseNonstocksJSON := c.FormValue("expense_nonstocks")
	if expenseNonstocksJSON != "" {
		var expenseNonstocks []validation.ExpenseNonstock
		if err := json.Unmarshal([]byte(expenseNonstocksJSON), &expenseNonstocks); err != nil {
			return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
		}

		req.ExpenseNonstocks = &expenseNonstocks
	}

	result, err := u.ExpenseService.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 expense successfully",
			Data:    result,
		})
}

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

	id64, err := strconv.ParseUint(param, 10, 64)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
	}

	if err := u.ExpenseService.DeleteOne(c, id64); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Delete expense successfully",
		})
}

func (u *ExpenseController) Approval(c *fiber.Ctx) error {
	req := new(validation.ApprovalRequest)

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

	path := c.Path()
	approvalType := ""
	if strings.Contains(path, "/approvals/head-area") {
		approvalType = "head-area"
	} else if strings.Contains(path, "/approvals/finance") {
		approvalType = "finance"
	} else if strings.Contains(path, "/approvals/unit-vice-president") {
		approvalType = "unit-vice-president"
	} else {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid approval path")
	}

	results, err := u.ExpenseService.Approval(c, req, approvalType)
	if err != nil {
		return err
	}

	var (
		data    interface{}
		message = "Submit expense approval successfully"
	)
	if len(results) == 1 {
		data = results[0]
	} else {
		message = "Submit expense approvals successfully"
		data = results
	}

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

func (u *ExpenseController) 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 REALISASI bulk approval")
	}

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

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

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

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

func (u *ExpenseController) CreateRealization(c *fiber.Ctx) error {
	expenseID := c.Params("id")
	id, err := strconv.Atoi(expenseID)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
	}

	req := new(validation.CreateRealization)

	form, err := c.MultipartForm()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
	}
	req.Documents = form.File["documents"]
	req.RealizationDate = c.FormValue("realization_date")

	realizationsJSON := c.FormValue("realizations")
	if realizationsJSON != "" {
		if err := json.Unmarshal([]byte(realizationsJSON), &req.Realizations); err != nil {
			return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid realizations JSON: %v", err))
		}
	}

	expense, err := u.ExpenseService.CreateRealization(c, uint(id), req)
	if err != nil {
		return err
	}

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

func (u *ExpenseController) UpdateRealization(c *fiber.Ctx) error {
	expenseID := c.Params("id")
	id, err := strconv.Atoi(expenseID)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
	}

	var req validation.UpdateRealization

	form, err := c.MultipartForm()
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
	}

	req.Documents = form.File["documents"]

	realizationDate := c.FormValue("realization_date")
	if realizationDate != "" {
		req.RealizationDate = &realizationDate
	}

	realizationsJSON := c.FormValue("realizations")
	if realizationsJSON != "" {
		var realizations []validation.RealizationItem
		if err := json.Unmarshal([]byte(realizationsJSON), &realizations); err != nil {
			return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid realizations JSON: %v", err))
		}
		req.Realizations = &realizations
	}

	expense, err := u.ExpenseService.UpdateRealization(c, uint(id), &req)
	if err != nil {
		return err
	}

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

func (u *ExpenseController) CompleteExpense(c *fiber.Ctx) error {
	expenseID := c.Params("id")
	id, err := strconv.Atoi(expenseID)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
	}

	expense, err := u.ExpenseService.CompleteExpense(c, uint(id), nil)
	if err != nil {
		return err
	}

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

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

	switch targetStep {
	case utils.ExpenseStepHeadArea:
		requiredPerms = []string{m.P_ExpenseApprovalHeadArea}
	case utils.ExpenseStepUnitVicePresident:
		requiredPerms = []string{m.P_ExpenseApprovalUnitVicePresident}
	case utils.ExpenseStepFinance:
		requiredPerms = []string{m.P_ExpenseApprovalFinance}
	case utils.ExpenseStepRealisasi:
		requiredPerms = []string{m.P_ExpenseApprovalFinance, m.P_ExpenseCreateRealizations}
	default:
		return fiber.NewError(fiber.StatusBadRequest, "Invalid approval target")
	}

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

	return nil
}

func (u *ExpenseController) DeleteDocument(c *fiber.Ctx) error {
	expenseID, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
	}

	documentID, err := strconv.ParseUint(c.Params("documentId"), 10, 64)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid document ID")
	}

	if err := u.ExpenseService.DeleteDocument(c, uint(expenseID), documentID, false); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Delete document successfully",
		})
}

func (u *ExpenseController) DeleteRealizationDocument(c *fiber.Ctx) error {
	expenseID, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
	}

	documentID, err := strconv.ParseUint(c.Params("documentId"), 10, 64)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid document ID")
	}

	if err := u.ExpenseService.DeleteDocument(c, uint(expenseID), documentID, true); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Delete realization document successfully",
		})
}
