package Admin

import (
	"astrology-api/constants"
	dto "astrology-api/dto_admin"
	"astrology-api/middleware"
	"astrology-api/services_admin"
	"astrology-api/utils"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
)

// The admin panel's finance manager.
//
// Four lists — pending, to-be-settled, settled and the batches — plus the
// review actions and the settlement run the panel's cron calls.
type SettlementController struct {
	settlementService services_admin.SettlementService
}

func NewSettlementController(
	settlementService services_admin.SettlementService,
) *SettlementController {

	return &SettlementController{
		settlementService: settlementService,
	}
}

//////////////////////////////////////////////////////////////
// Lists
//////////////////////////////////////////////////////////////

// filterFrom reads the list filters off the query string, the way the other
// paginated lists in this API read theirs.
func filterFrom(ctx *gin.Context) dto.SettlementListFilter {

	filter := dto.SettlementListFilter{
		Page:             1,
		Limit:            10,
		Search:           ctx.DefaultQuery("search", ""),
		SettlementStatus: ctx.DefaultQuery("settlement_status", "ALL"),
		Medium:           ctx.DefaultQuery("medium", "ALL"),
		FromDate:         ctx.DefaultQuery("from_date", ""),
		ToDate:           ctx.DefaultQuery("to_date", ""),
		SortBy:           ctx.DefaultQuery("sort_by", ""),
		SortDir:          ctx.DefaultQuery("sort_dir", "desc"),
	}

	if value, err := strconv.Atoi(ctx.DefaultQuery("page", "1")); err == nil && value > 0 {
		filter.Page = value
	}

	if value, err := strconv.Atoi(ctx.DefaultQuery("limit", "10")); err == nil && value > 0 {
		filter.Limit = value
	}

	if value, err := strconv.Atoi(ctx.DefaultQuery("astrologer_id", "0")); err == nil && value > 0 {
		filter.AstrologerID = uint(value)
	}

	if value, err := strconv.Atoi(ctx.DefaultQuery("user_id", "0")); err == nil && value > 0 {
		filter.UserID = uint(value)
	}

	return filter
}

// GetConsultations lists consultations across every settlement status. The
// media history screens use this with a medium filter.
//
// GET /api/admin/settlement/consultations
func (c *SettlementController) GetConsultations(ctx *gin.Context) {

	response, err := c.settlementService.ListConsultations(filterFrom(ctx))

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Consultations", response)
}

// GetPending is the review queue: billed sessions whose earning nobody has
// released yet.
//
// GET /api/admin/settlement/pending
func (c *SettlementController) GetPending(ctx *gin.Context) {

	filter := filterFrom(ctx)
	filter.SettlementStatus = constants.SettlementStatusPending

	response, err := c.settlementService.ListConsultations(filter)

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Settlement pending consultations", response)
}

// GetToBeSettled is what the next run will pay out.
//
// GET /api/admin/settlement/to-be-settled
func (c *SettlementController) GetToBeSettled(ctx *gin.Context) {

	filter := filterFrom(ctx)
	filter.SettlementStatus = constants.SettlementStatusToBeSettled

	response, err := c.settlementService.ListConsultations(filter)

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Consultations to be settled", response)
}

// GetSettled is the settled history at consultation level — every session a
// batch has closed.
//
// GET /api/admin/settlement/settled
func (c *SettlementController) GetSettled(ctx *gin.Context) {

	filter := filterFrom(ctx)
	filter.SettlementStatus = constants.SettlementStatusSettled

	response, err := c.settlementService.ListConsultations(filter)

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Settled consultations", response)
}

// GetOnHold lists the parked rows.
//
// GET /api/admin/settlement/on-hold
func (c *SettlementController) GetOnHold(ctx *gin.Context) {

	filter := filterFrom(ctx)
	filter.SettlementStatus = constants.SettlementStatusOnHold

	response, err := c.settlementService.ListConsultations(filter)

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Consultations on hold", response)
}

//////////////////////////////////////////////////////////////
// Detail
//////////////////////////////////////////////////////////////

// GetConsultationDetail is the review screen: the session, the money split,
// the customer's review and every settlement transition so far.
//
// GET /api/admin/settlement/consultation/:id
func (c *SettlementController) GetConsultationDetail(ctx *gin.Context) {

	id, err := strconv.Atoi(ctx.Param("id"))

	if err != nil || id <= 0 {
		utils.BadRequest(ctx, "Invalid consultation id")
		return
	}

	response, err := c.settlementService.GetConsultationDetail(uint(id))

	if err != nil {

		if strings.Contains(err.Error(), "not found") {
			utils.NotFound(ctx, err.Error())
			return
		}

		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Consultation detail", response)
}

//////////////////////////////////////////////////////////////
// Review actions
//////////////////////////////////////////////////////////////

// Review applies the admin's decision to one or many consultations: APPROVE
// (release for settlement), HOLD, REJECT or REVERT.
//
// POST /api/admin/settlement/review
func (c *SettlementController) Review(ctx *gin.Context) {

	var request dto.ReviewSettlementRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {
		utils.BadRequest(ctx, err.Error())
		return
	}

	response, err := c.settlementService.ReviewConsultations(
		request,
		middleware.GetAdminID(ctx),
		middleware.IsSystemCaller(ctx),
	)

	if err != nil {
		utils.BadRequest(ctx, err.Error())
		return
	}

	utils.Success(ctx, "Settlement status updated", response)
}

// Approve is the common case with its own route, so the panel's "Approve
// selected" button does not have to send an action string.
//
// POST /api/admin/settlement/approve
func (c *SettlementController) Approve(ctx *gin.Context) {
	c.reviewWithAction(ctx, services_admin.ActionApprove, "Consultations approved for settlement")
}

// POST /api/admin/settlement/hold
func (c *SettlementController) Hold(ctx *gin.Context) {
	c.reviewWithAction(ctx, services_admin.ActionHold, "Consultations put on hold")
}

// POST /api/admin/settlement/reject
func (c *SettlementController) Reject(ctx *gin.Context) {
	c.reviewWithAction(ctx, services_admin.ActionReject, "Consultations rejected")
}

// POST /api/admin/settlement/revert
func (c *SettlementController) Revert(ctx *gin.Context) {
	c.reviewWithAction(ctx, services_admin.ActionRevert, "Consultations reverted to pending")
}

func (c *SettlementController) reviewWithAction(
	ctx *gin.Context,
	action string,
	message string,
) {

	var request dto.ReviewSettlementRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {
		utils.BadRequest(ctx, err.Error())
		return
	}

	// The route decides the action, not the body.
	request.Action = action

	response, err := c.settlementService.ReviewConsultations(
		request,
		middleware.GetAdminID(ctx),
		middleware.IsSystemCaller(ctx),
	)

	if err != nil {
		utils.BadRequest(ctx, err.Error())
		return
	}

	utils.Success(ctx, message, response)
}

//////////////////////////////////////////////////////////////
// The settlement job
//////////////////////////////////////////////////////////////

// RunSettlement settles every approved consultation into per-astrologer
// batches and credits the wallets. This is the endpoint the admin panel's
// cron calls on the configured schedule; it also backs the panel's manual
// "Run now" button.
//
// Safe to call twice: consultations are claimed under a row lock, so a second
// run finds nothing left and reports it.
//
// POST /api/admin/settlement/run
func (c *SettlementController) RunSettlement(ctx *gin.Context) {

	var request dto.RunSettlementRequest

	// An empty body is the normal cron call: settle everything approved.
	if ctx.Request.ContentLength > 0 {

		if err := ctx.ShouldBindJSON(&request); err != nil {
			utils.BadRequest(ctx, err.Error())
			return
		}
	}

	response, err := c.settlementService.RunSettlement(
		request,
		middleware.GetAdminID(ctx),
		middleware.IsSystemCaller(ctx),
	)

	if err != nil {
		utils.BadRequest(ctx, err.Error())
		return
	}

	message := "Settlement completed"

	if response.DryRun {
		message = "Settlement preview"
	}

	utils.Success(ctx, message, response)
}

// SweepStaleConsultations closes and bills sessions whose balance ticks
// stopped arriving. Meant for a short-interval cron — every few minutes —
// separate from the settlement run.
//
// POST /api/admin/consultation/sweep-stale
func (c *SettlementController) SweepStaleConsultations(ctx *gin.Context) {

	limit := 100

	if value, err := strconv.Atoi(ctx.DefaultQuery("limit", "100")); err == nil && value > 0 {
		limit = value
	}

	response, err := c.settlementService.SweepStaleConsultations(limit)

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Stale consultations closed", response)
}

//////////////////////////////////////////////////////////////
// Settlement history
//////////////////////////////////////////////////////////////

// GetSettlementHistory lists the batches the job wrote — one row per
// astrologer per run.
//
// GET /api/admin/settlement/history
func (c *SettlementController) GetSettlementHistory(ctx *gin.Context) {

	page := 1
	limit := 10

	if value, err := strconv.Atoi(ctx.DefaultQuery("page", "1")); err == nil && value > 0 {
		page = value
	}

	if value, err := strconv.Atoi(ctx.DefaultQuery("limit", "10")); err == nil && value > 0 {
		limit = value
	}

	astrologerID := uint(0)

	if value, err := strconv.Atoi(ctx.DefaultQuery("astrologer_id", "0")); err == nil && value > 0 {
		astrologerID = uint(value)
	}

	response, err := c.settlementService.ListSettlementBatches(
		astrologerID,
		ctx.DefaultQuery("search", ""),
		ctx.DefaultQuery("from_date", ""),
		ctx.DefaultQuery("to_date", ""),
		page,
		limit,
	)

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Settlement history", response)
}

// GetSettlementDetail opens one batch and lists the consultations it closed.
//
// GET /api/admin/settlement/history/:id
func (c *SettlementController) GetSettlementDetail(ctx *gin.Context) {

	id, err := strconv.Atoi(ctx.Param("id"))

	if err != nil || id <= 0 {
		utils.BadRequest(ctx, "Invalid settlement id")
		return
	}

	response, err := c.settlementService.GetSettlementBatch(uint(id))

	if err != nil {

		if strings.Contains(err.Error(), "not found") {
			utils.NotFound(ctx, err.Error())
			return
		}

		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Settlement detail", response)
}

//////////////////////////////////////////////////////////////
// Summary
//////////////////////////////////////////////////////////////

// GetFinanceSummary is the finance manager's header: what is waiting on
// review, what the next run owes, what has been paid and what is still sitting
// in astrologer wallets.
//
// GET /api/admin/settlement/summary
func (c *SettlementController) GetFinanceSummary(ctx *gin.Context) {

	response, err := c.settlementService.GetFinanceSummary()

	if err != nil {
		utils.InternalServerError(ctx, err)
		return
	}

	utils.Success(ctx, "Finance summary", response)
}
