package services_admin

import (
	config "astrology-api/configs"
	"astrology-api/constants"
	dto "astrology-api/dto_admin"
	"errors"
	"strconv"
)

// The chat screen's settings.
//
// A separate endpoint from /settings/general rather than more fields on it:
// UpdateGeneralSettings is already two hundred lines of per-field validation,
// its response carries derived settlement-schedule output that chat has
// nothing to do with, and one rejected chat value would otherwise abort a
// settlement-schedule save that happened to be in the same body.
//
// The Agora credentials are reported but never written. The certificate is a
// signing key — whoever holds it can mint a token for any channel and join any
// session — so it lives in the environment beside ADMIN_API_KEY, not in
// systemflag where every flag reader and every database dump would carry it.

func (s *settingsService) GetChatSettings() (*dto.ChatSettingsResponse, error) {

	values, err := s.repository.GetSystemFlags(names(chatFlags))

	if err != nil {
		return nil, err
	}

	// The general tick timeout is read too, so the panel can show what a
	// chat's own timeout is falling back to.
	general, err := s.repository.GetSystemFlags([]string{constants.FlagConsultationTickTimeoutSecs})

	if err != nil {
		return nil, err
	}

	response := &dto.ChatSettingsResponse{
		ChatConsultationEnabled: flagFloat(values, constants.FlagChatConsultationEnabled, 1) == 1,

		FreeChatEnabled:             flagFloat(values, constants.FlagFreeChatEnabled, 1) == 1,
		FreeChatMinutes:             int(flagFloat(values, constants.FlagFreeChatMinutes, 2)),
		FreeChatMinConnectedSeconds: int(flagFloat(values, constants.FlagFreeChatMinConnectedSeconds, 10)),

		// Read-only. The rule is enforced by a query over the sessions
		// themselves plus a unique index, not by configuration: making it
		// switchable would change the shape of that query at runtime, which
		// is how a customer ends up with unlimited free chats.
		FreeChatScope: "LIFETIME",

		ConsultationRingTimeoutSecs: int(flagFloat(values, constants.FlagConsultationRingTimeoutSecs, 60)),
		ConsultationJoinTimeoutSecs: int(flagFloat(values, constants.FlagConsultationJoinTimeoutSecs, 60)),
		ConsultationWarningSeconds:  int(flagFloat(values, constants.FlagConsultationWarningSeconds, 30)),
		ConsultationMaxMinutes:      int(flagFloat(values, constants.FlagConsultationMaxMinutes, 60)),

		ChatTickTimeoutSecs:         int(flagFloat(values, constants.FlagChatTickTimeoutSecs, 240)),
		ConsultationTickTimeoutSecs: int(flagFloat(general, constants.FlagConsultationTickTimeoutSecs, 120)),

		ConsultationAvailabilityStrict: flagFloat(values, constants.FlagConsultationAvailabilityStrict, 0) == 1,

		ChatTranscriptWindowHours: int(flagFloat(values, constants.FlagChatTranscriptWindowHours, 72)),

		AgoraAppID:                 config.Agora.AppID,
		AgoraCertificateConfigured: config.Agora.AppCertificate != "",
		AgoraConfigured:            config.Agora.Enabled(),
	}

	if s.consultations != nil {

		if claimed, cost, err := s.consultations.FreeChatTotals(); err == nil {
			response.FreeChatClaimedCount = claimed
			response.FreeChatCostThisMonth = cost
		}
	}

	return response, nil
}

func (s *settingsService) UpdateChatSettings(
	request dto.UpdateChatSettingsRequest,
) (*dto.ChatSettingsResponse, error) {

	// Staged before anything is written, so one rejected value cannot leave
	// half the screen saved.
	writes := map[string]string{}

	if request.ChatConsultationEnabled != nil {
		writes[constants.FlagChatConsultationEnabled] = boolFlag(*request.ChatConsultationEnabled)
	}

	if request.FreeChatEnabled != nil {
		writes[constants.FlagFreeChatEnabled] = boolFlag(*request.FreeChatEnabled)
	}

	if request.ConsultationAvailabilityStrict != nil {
		writes[constants.FlagConsultationAvailabilityStrict] = boolFlag(*request.ConsultationAvailabilityStrict)
	}

	// The platform pays for these minutes, and nothing anywhere else stops an
	// admin typing 500.
	if value := request.FreeChatMinutes; value != nil {

		if *value < 0 || *value > 15 {
			return nil, errors.New("free chat minutes must be between 0 and 15")
		}

		writes[constants.FlagFreeChatMinutes] = strconv.Itoa(*value)
	}

	if value := request.FreeChatMinConnectedSeconds; value != nil {

		if *value < 0 || *value > 120 {
			return nil, errors.New("free chat minimum connected seconds must be between 0 and 120")
		}

		writes[constants.FlagFreeChatMinConnectedSeconds] = strconv.Itoa(*value)
	}

	// Below fifteen seconds an astrologer physically cannot reach the Accept
	// button before the request expires.
	//
	// The ceiling is loose because a long ring is a legitimate testing value —
	// driving the flow by hand takes minutes, not seconds — and because the
	// cost of a long one is a customer staring at a spinner rather than
	// anything unsafe. Anything above a couple of minutes is a testing value
	// though, not a production one: a request that is still ringing also
	// counts the astrologer as busy, so one unanswered tap makes them
	// unreachable to everyone else for the whole window.
	if value := request.ConsultationRingTimeoutSecs; value != nil {

		if *value < 15 || *value > 900 {
			return nil, errors.New("consultation ring timeout must be between 15 and 900 seconds")
		}

		writes[constants.FlagConsultationRingTimeoutSecs] = strconv.Itoa(*value)
	}

	if value := request.ConsultationJoinTimeoutSecs; value != nil {

		if *value < 15 || *value > 900 {
			return nil, errors.New("consultation join timeout must be between 15 and 900 seconds")
		}

		writes[constants.FlagConsultationJoinTimeoutSecs] = strconv.Itoa(*value)
	}

	if value := request.ConsultationWarningSeconds; value != nil {

		if *value < 10 || *value > 120 {
			return nil, errors.New("consultation warning seconds must be between 10 and 120")
		}

		writes[constants.FlagConsultationWarningSeconds] = strconv.Itoa(*value)
	}

	if value := request.ConsultationMaxMinutes; value != nil {

		if *value != 0 && (*value < 5 || *value > 480) {
			return nil, errors.New("consultation maximum minutes must be 0 or between 5 and 480")
		}

		writes[constants.FlagConsultationMaxMinutes] = strconv.Itoa(*value)
	}

	// A minute is the floor: below that an ordinary network hiccup would
	// start closing and billing live sessions.
	if value := request.ChatTickTimeoutSecs; value != nil {

		if *value != 0 && *value < 60 {
			return nil, errors.New("chat tick timeout must be 0 or at least 60 seconds")
		}

		writes[constants.FlagChatTickTimeoutSecs] = strconv.Itoa(*value)
	}

	if value := request.ChatTranscriptWindowHours; value != nil {

		if *value < 0 || *value > 8760 {
			return nil, errors.New("chat transcript window hours must be between 0 and 8760")
		}

		writes[constants.FlagChatTranscriptWindowHours] = strconv.Itoa(*value)
	}

	for name, value := range writes {

		valueType, displayName := flagMeta(name)

		if err := s.repository.SetSystemFlag(name, value, valueType, displayName); err != nil {
			return nil, err
		}
	}

	return s.GetChatSettings()
}

func boolFlag(value bool) string {

	if value {
		return "1"
	}

	return "0"
}
