package dto

// The request -> accept -> start handshake.
//
// Three steps, and the money only starts at the third:
//
//	request     the customer asks. Nothing is billed, no channel exists, no
//	            token is minted. The astrologer's app rings.
//
//	accept      the astrologer says "I am available". They are marked busy and
//	            both sides get credentials — but the clock does not run yet,
//	            because the customer may still be reading the notification.
//
//	start-chat  the customer confirms. THIS is where startedAt is written, the
//	            cap is recomputed against the live wallet, and billing begins.
//
// The gap between accept and start is deliberately free. An astrologer who
// accepts and waits forty seconds must not burn forty seconds of that
// customer's wallet, or a third of their free chat.

//------------------------------------------------
// Precheck
//------------------------------------------------

// PrecheckResponse is what the "Start chat" sheet renders before the customer
// commits to anything. It is the only place the free-chat entitlement, the
// required balance and the astrologer's live availability are resolved
// together, server-side.
//
// Do not cache it: the balance and the online state both move.
type PrecheckResponse struct {
	AstrologerID   uint   `json:"astrologer_id"`
	AstrologerName string `json:"astrologer_name"`
	Medium         string `json:"medium"`

	CanStart            bool    `json:"can_start"`
	BlockedReason       string  `json:"blocked_reason"`
	BlockedMessage      string  `json:"blocked_message"`
	IsFreeChat          bool    `json:"is_free_chat"`
	FreeMinutes         int     `json:"free_minutes"`
	RatePerMinute       float64 `json:"rate_per_minute"`
	WalletBalance       float64 `json:"wallet_balance"`
	RequiredBalance     float64 `json:"required_balance"`
	Shortfall           float64 `json:"shortfall"`
	MaxBillableSeconds  int     `json:"max_billable_seconds"`
	MaxBillableMinutes  int     `json:"max_billable_minutes"`
	RingTimeoutSeconds  int     `json:"ring_timeout_seconds"`
	HasOpenConsultation bool    `json:"has_open_consultation"`
	OpenConsultationID  uint    `json:"open_consultation_id,omitempty"`
}

//------------------------------------------------
// Request
//------------------------------------------------

// RequestConsultationResponse is the "waiting for the astrologer" screen.
//
// Carries no Agora block on purpose: a request that is never accepted never
// mints a token.
type RequestConsultationResponse struct {
	ConsultationID uint   `json:"consultation_id"`
	ConsultationNo string `json:"consultation_no"`

	AstrologerID    uint   `json:"astrologer_id"`
	AstrologerName  string `json:"astrologer_name"`
	AstrologerImage string `json:"astrologer_image,omitempty"`

	Medium string `json:"medium"`
	Status string `json:"status"`

	IsFreeChat  bool `json:"is_free_chat"`
	FreeMinutes int  `json:"free_minutes"`

	RatePerMinute float64 `json:"rate_per_minute"`
	WalletBalance float64 `json:"wallet_balance"`

	// A quote, not a grant. The binding cap is recomputed when the customer
	// starts the session, because the balance can move while the phone rings.
	// Show it as "up to about N minutes" and read the real value from the
	// start-chat response.
	QuotedMaxBillableSeconds int `json:"quoted_max_billable_seconds"`
	QuotedMaxBillableMinutes int `json:"quoted_max_billable_minutes"`

	RequestedAt        string `json:"requested_at"`
	RingExpiresAt      string `json:"ring_expires_at"`
	RingTimeoutSeconds int    `json:"ring_timeout_seconds"`

	PollIntervalSeconds int `json:"poll_interval_seconds"`
}

//------------------------------------------------
// Status
//------------------------------------------------

// ConsultationStatusResponse is polled while the request rings, and read once
// more on accept. It expires its own row: polling it past the ring timeout
// flips the session to MISSED then and there, so the customer's countdown and
// the server agree without waiting for the sweep.
type ConsultationStatusResponse struct {
	ConsultationID uint   `json:"consultation_id"`
	ConsultationNo string `json:"consultation_no"`

	Status    string `json:"status"`
	EndReason string `json:"end_reason,omitempty"`

	AstrologerID    uint   `json:"astrologer_id"`
	AstrologerName  string `json:"astrologer_name"`
	AstrologerImage string `json:"astrologer_image,omitempty"`

	Medium string `json:"medium"`

	IsFreeChat  bool `json:"is_free_chat"`
	FreeMinutes int  `json:"free_minutes"`

	RatePerMinute float64 `json:"rate_per_minute"`
	WalletBalance float64 `json:"wallet_balance"`

	RequestedAt string `json:"requested_at,omitempty"`
	AcceptedAt  string `json:"accepted_at,omitempty"`
	StartedAt   string `json:"started_at,omitempty"`

	// Counts down while REQUESTED.
	RingExpiresAt   string `json:"ring_expires_at,omitempty"`
	SecondsToExpiry int    `json:"seconds_to_expiry,omitempty"`

	// Counts down while ACCEPTED: how long the customer has to tap Start
	// chat before the astrologer is released.
	JoinExpiresAt    string `json:"join_expires_at,omitempty"`
	SecondsToJoinEnd int    `json:"seconds_to_join_end,omitempty"`

	// Populated once the session is running.
	ElapsedSeconds     int     `json:"elapsed_seconds,omitempty"`
	RemainingSeconds   int     `json:"remaining_seconds,omitempty"`
	MaxBillableSeconds int     `json:"max_billable_seconds,omitempty"`
	CurrentCharge      float64 `json:"current_charge,omitempty"`

	ChannelName string `json:"channel_name,omitempty"`

	TickIntervalSeconds int `json:"tick_interval_seconds,omitempty"`
	TickTimeoutSeconds  int `json:"tick_timeout_seconds,omitempty"`
	WarningSeconds      int `json:"warning_seconds,omitempty"`

	// Present only once the customer has started the session. The accepted
	// state deliberately hands out nothing to join with — the customer has
	// not yet agreed to be charged.
	Agora *AgoraCredentials `json:"agora,omitempty"`

	// The receipt, once the session is over.
	Receipt *EndConsultationResponse `json:"receipt,omitempty"`
}

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

type StartChatRequest struct {
	ConsultationID uint `json:"consultation_id" binding:"required"`
}

// StartChatResponse is where the clock starts and the credentials arrive.
//
// MaxBillableSeconds here is authoritative — recomputed against the wallet as
// it stands at this instant. Both apps must read the cap from this response
// and not from the request or accept ones, which are quotes.
type StartChatResponse struct {
	ConsultationID uint   `json:"consultation_id"`
	ConsultationNo string `json:"consultation_no"`

	AstrologerID    uint   `json:"astrologer_id"`
	AstrologerName  string `json:"astrologer_name"`
	AstrologerImage string `json:"astrologer_image,omitempty"`

	Medium string `json:"medium"`
	Status string `json:"status"`

	IsFreeChat  bool `json:"is_free_chat"`
	FreeMinutes int  `json:"free_minutes"`

	RatePerMinute float64 `json:"rate_per_minute"`
	WalletBalance float64 `json:"wallet_balance"`

	MaxBillableSeconds int `json:"max_billable_seconds"`
	MaxBillableMinutes int `json:"max_billable_minutes"`

	StartedAt string `json:"started_at"`

	// The server's clock at the moment of the reply. Anchor the countdown to
	// this rather than to the device clock: the server bills from startedAt,
	// and a device four minutes fast otherwise shows four minutes the
	// customer will dispute.
	ServerTime string `json:"server_time"`

	ChannelName string `json:"channel_name"`

	TickIntervalSeconds int `json:"tick_interval_seconds"`
	TickTimeoutSeconds  int `json:"tick_timeout_seconds"`
	WarningSeconds      int `json:"warning_seconds"`

	Agora *AgoraCredentials `json:"agora,omitempty"`
}

//------------------------------------------------
// Astrologer side
//------------------------------------------------

type AcceptConsultationRequest struct {
	ConsultationID uint `json:"consultation_id" binding:"required"`
}

type RejectConsultationRequest struct {
	ConsultationID uint `json:"consultation_id" binding:"required"`

	// Shown to the customer verbatim, so keep it short and civil.
	Reason string `json:"reason"`
}

// AcceptConsultationResponse tells the astrologer what they just committed to.
//
// The astrologer joins the channel on accept, but the customer has not
// confirmed yet, so the clock is not running: StartedAt stays empty until they
// do, and JoinExpiresAt says how long the astrologer is held.
type AcceptConsultationResponse struct {
	ConsultationID uint   `json:"consultation_id"`
	ConsultationNo string `json:"consultation_no"`

	UserID    uint   `json:"user_id"`
	UserName  string `json:"user_name"`
	UserImage string `json:"user_image,omitempty"`

	Medium string `json:"medium"`
	Status string `json:"status"`

	IsFreeChat  bool `json:"is_free_chat"`
	FreeMinutes int  `json:"free_minutes"`

	RatePerMinute float64 `json:"rate_per_minute"`

	// What the astrologer stands to earn if the session runs its full cap,
	// after commission. On a free chat the platform pays this, so it is not
	// zero — the app should say "Free for customer, you earn X" rather than
	// let an astrologer think they are working for nothing.
	EstimatedEarning float64 `json:"estimated_earning"`

	QuotedMaxBillableSeconds int `json:"quoted_max_billable_seconds"`
	QuotedMaxBillableMinutes int `json:"quoted_max_billable_minutes"`

	AcceptedAt       string `json:"accepted_at"`
	ServerTime       string `json:"server_time"`
	JoinExpiresAt    string `json:"join_expires_at"`
	SecondsToJoinEnd int    `json:"seconds_to_join_end"`

	ChannelName string `json:"channel_name"`

	ConsulteeName       string `json:"consultee_name,omitempty"`
	ConsulteeBirthDate  string `json:"consultee_birth_date,omitempty"`
	ConsulteeBirthTime  string `json:"consultee_birth_time,omitempty"`
	ConsulteeBirthPlace string `json:"consultee_birth_place,omitempty"`
	ConsulteeGender     string `json:"consultee_gender,omitempty"`

	Agora *AgoraCredentials `json:"agora,omitempty"`
}

// IncomingRequestItem is one row of the astrologer's ringing queue.
type IncomingRequestItem struct {
	ConsultationID uint   `json:"consultation_id"`
	ConsultationNo string `json:"consultation_no"`

	UserID    uint   `json:"user_id"`
	UserName  string `json:"user_name"`
	UserImage string `json:"user_image,omitempty"`

	Medium string `json:"medium"`
	Status string `json:"status"`

	IsFreeChat  bool `json:"is_free_chat"`
	FreeMinutes int  `json:"free_minutes"`

	RatePerMinute    float64 `json:"rate_per_minute"`
	EstimatedEarning float64 `json:"estimated_earning"`

	QuotedMaxBillableSeconds int `json:"quoted_max_billable_seconds"`
	QuotedMaxBillableMinutes int `json:"quoted_max_billable_minutes"`

	ConsulteeName       string `json:"consultee_name,omitempty"`
	ConsulteeBirthDate  string `json:"consultee_birth_date,omitempty"`
	ConsulteeBirthTime  string `json:"consultee_birth_time,omitempty"`
	ConsulteeBirthPlace string `json:"consultee_birth_place,omitempty"`
	ConsulteeGender     string `json:"consultee_gender,omitempty"`

	RequestedAt     string `json:"requested_at"`
	RingExpiresAt   string `json:"ring_expires_at"`
	SecondsToExpiry int    `json:"seconds_to_expiry"`
}

type IncomingRequestsResponse struct {
	Items []IncomingRequestItem `json:"items"`
	Count int                   `json:"count"`

	// How many requests this very call found already rung out and closed.
	//
	// Worth reporting rather than swallowing: an empty queue with
	// expired_count above zero means requests ARE arriving and the astrologer
	// is not answering in time — a completely different problem from an empty
	// queue with zero, which means nothing arrived at all. Without it both
	// look identical, and the obvious next step is to go hunting for a bug
	// that is not there.
	ExpiredCount int `json:"expired_count"`

	// The window an astrologer has to answer. Drive the countdown on the
	// incoming sheet from this rather than hard-coding 60, so raising it for
	// testing does not need an app build.
	RingTimeoutSeconds int `json:"ring_timeout_seconds"`

	ServerTime          string `json:"server_time"`
	PollIntervalSeconds int    `json:"poll_interval_seconds"`
}

//------------------------------------------------
// Continue a free chat on the paid rate
//------------------------------------------------

type ContinuePaidRequest struct {
	ConsultationID uint `json:"consultation_id" binding:"required"`
}

//------------------------------------------------
// Sweep
//------------------------------------------------

// SweepResult is what the admin panel's scheduler gets back. Each pass
// reports separately, and a pass that fails is counted rather than aborting
// the others — one bad session must never stop the sweep.
type SweepResult struct {
	Warned         int `json:"warned"`
	Capped         int `json:"capped"`
	Abandoned      int `json:"abandoned"`
	Missed         int `json:"missed"`
	Unjoined       int `json:"unjoined"`
	BusyReconciled int `json:"busy_reconciled"`

	// The waiting queue's timeouts and FIFO hand-over.
	Queue QueueSweepResult `json:"queue"`

	Errors []string `json:"errors,omitempty"`

	Limit int    `json:"limit"`
	RunAt string `json:"run_at"`
}
