package User

import (
	dto "astrology-api/dto/consultation"
	"astrology-api/middleware"
	services "astrology-api/services"
	"astrology-api/utils"
	"strconv"

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

// ConsultationController is the customer's side of a paid session: connect to
// an astrologer, stay connected while the wallet can pay for it, and get
// billed when it ends.
//
// Nothing here accepts an amount, a rate or a duration it then trusts. The
// rate comes off the astrologer row, the fee off systemflag, and the duration
// off the session's own timestamps.
type ConsultationController struct {
	consultationService services.ConsultationService
}

func NewConsultationController(
	consultationService services.ConsultationService,
) *ConsultationController {

	return &ConsultationController{
		consultationService: consultationService,
	}
}

//////////////////////////////////////////////////////////////
// Start
//////////////////////////////////////////////////////////////

// StartConsultation REQUESTS a chat / audio / video session.
//
// The route kept its URL but the meaning changed: it returns REQUESTED, bills
// nothing and mints no token. The astrologer has to accept and the customer
// has to confirm with /consultation/start-chat before any money moves.
//
// POST /api/user/consultation/start
func (c *ConsultationController) StartConsultation(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.StartConsultationRequest

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

	response, err := c.consultationService.StartConsultation(userID, request)

	if err != nil {

		// Everything the service rejects here is the customer's situation —
		// no balance, no rate, a session already running — not a server
		// fault, so it comes back as a 400 the app can show.
		utils.BadRequest(ctx, err.Error())
		return
	}

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

//////////////////////////////////////////////////////////////
// Precheck
//////////////////////////////////////////////////////////////

// Precheck renders the "Start chat" sheet before the customer commits to
// anything: whether the session can start, whether it is free, and how long
// the balance would pay for.
//
// GET /api/user/consultation/precheck?astrologer_id=&medium=
func (c *ConsultationController) Precheck(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	astrologerID, _ := strconv.ParseUint(ctx.Query("astrologer_id"), 10, 64)

	if astrologerID == 0 {
		utils.BadRequest(ctx, "astrologer_id is required")
		return
	}

	medium := ctx.DefaultQuery("medium", "CHAT")

	response, err := c.consultationService.Precheck(userID, uint(astrologerID), medium)

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

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

//////////////////////////////////////////////////////////////
// Start the chat (the customer's confirmation)
//////////////////////////////////////////////////////////////

// StartChat is what the customer taps once the astrologer has accepted. It is
// the moment the clock starts and the credentials are handed over — until
// then nothing has been billed.
//
// POST /api/user/consultation/start-chat
func (c *ConsultationController) StartChat(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.StartChatRequest

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

	response, err := c.consultationService.StartChat(userID, request.ConsultationID)

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

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

//////////////////////////////////////////////////////////////
// Status
//////////////////////////////////////////////////////////////

// GetConsultationStatus is polled while the request rings, and read once more
// when it is accepted.
//
// GET /api/user/consultation/status?consultation_id=
func (c *ConsultationController) GetConsultationStatus(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	consultationID, _ := strconv.ParseUint(ctx.Query("consultation_id"), 10, 64)

	if consultationID == 0 {
		utils.BadRequest(ctx, "consultation_id is required")
		return
	}

	response, err := c.consultationService.GetConsultationStatus(userID, uint(consultationID))

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

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

//////////////////////////////////////////////////////////////
// Cancel
//////////////////////////////////////////////////////////////

// CancelConsultation withdraws a request that is still ringing. Nothing is
// billed and the free chat, if one was in play, is untouched.
//
// POST /api/user/consultation/cancel
func (c *ConsultationController) CancelConsultation(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.TickRequest

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

	response, err := c.consultationService.CancelConsultation(userID, request.ConsultationID)

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

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

//////////////////////////////////////////////////////////////
// Continue and extend
//////////////////////////////////////////////////////////////

// ContinuePaid carries a free chat on at the astrologer's rate. This is what
// the customer taps on the "your free minutes are ending" prompt.
//
// POST /api/user/consultation/continue-paid
func (c *ConsultationController) ContinuePaid(ctx *gin.Context) {

	c.raise(ctx, c.consultationService.ContinuePaid, "Consultation continued")
}

// ExtendConsultation re-reads the wallet after a recharge and raises the cap,
// so a customer who topped up mid-session is not still cut off on the old one.
//
// POST /api/user/consultation/extend
func (c *ConsultationController) ExtendConsultation(ctx *gin.Context) {

	c.raise(ctx, c.consultationService.ExtendConsultation, "Consultation extended")
}

func (c *ConsultationController) raise(
	ctx *gin.Context,
	action func(uint, uint) (*dto.TickResponse, error),
	message string,
) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.TickRequest

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

	response, err := action(userID, request.ConsultationID)

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

	utils.Success(ctx, message, response)
}

//////////////////////////////////////////////////////////////
// Agora
//////////////////////////////////////////////////////////////

// GetAgoraToken mints a fresh token for a session the caller is a party to.
// The app calls it from the SDK's token-expiring callback.
//
// POST /api/user/consultation/agora-token
func (c *ConsultationController) GetAgoraToken(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.TickRequest

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

	response, err := c.consultationService.CustomerAgoraToken(userID, request.ConsultationID)

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

	utils.Success(ctx, "Agora token", response)
}

//////////////////////////////////////////////////////////////
// Tick
//////////////////////////////////////////////////////////////

// Tick is the periodic balance check while the session runs. The app calls it
// on a timer and disconnects when should_disconnect comes back true.
//
// POST /api/user/consultation/tick
func (c *ConsultationController) Tick(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.TickRequest

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

	response, err := c.consultationService.Tick(userID, request.ConsultationID)

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

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

//////////////////////////////////////////////////////////////
// End
//////////////////////////////////////////////////////////////

// EndConsultation closes the session and bills it. The response is the
// customer's receipt; the astrologer's share is reported as owed, and is
// credited only after admin review and the settlement run.
//
// POST /api/user/consultation/end
func (c *ConsultationController) EndConsultation(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	var request dto.EndConsultationRequest

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

	response, err := c.consultationService.EndConsultation(userID, request)

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

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

//////////////////////////////////////////////////////////////
// Active
//////////////////////////////////////////////////////////////

// GetActiveConsultation lets an app that was killed mid-session rejoin instead
// of starting a second one.
//
// GET /api/user/consultation/active
func (c *ConsultationController) GetActiveConsultation(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	response, err := c.consultationService.GetActiveConsultation(userID)

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

	utils.Success(ctx, "Active consultation", response)
}

//////////////////////////////////////////////////////////////
// History
//////////////////////////////////////////////////////////////

// GetHistory lists the customer's past sessions. page / limit / medium come
// off the query string, the way the other paginated lists in this API read
// them, even though the route is a POST.
//
// POST /api/user/consultation/history
func (c *ConsultationController) GetHistory(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {
		utils.Unauthorized(ctx, "Unauthorized")
		return
	}

	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
	}

	medium := ctx.DefaultQuery("medium", "ALL")

	response, err := c.consultationService.GetHistory(userID, medium, page, limit)

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

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