package services

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// MSG91Service handles SMS gateway communication.
type MSG91Service struct {
	AuthKey       string
	APIURL        string
	Sender        string
	DLTTemplateID string
	PeID          string
	Route         string
	Country       string
}

// MSG91SMSRequest contains all dynamic SMS parameters.
type MSG91SMSRequest struct {
	AuthKey       string
	APIURL        string
	Mobile        string
	OTP           string
	Message       string
	Sender        string
	DLTTemplateID string
	PeID          string
	Route         string
	Country       string
}

// MSG91SMSResponse contains the gateway response.
type MSG91SMSResponse struct {
	Success     bool
	StatusCode  int
	Message     string
	RawResponse string
}

func NewMSG91Service() *MSG91Service {

	config := ResolveMSG91Config()

	return &MSG91Service{
		AuthKey:       config.AuthKey,
		APIURL:        config.APIURL,
		Sender:        config.Sender,
		DLTTemplateID: config.DLTTemplateID,
		PeID:          config.PeID,
		Route:         config.Route,
		Country:       config.Country,
	}
}

func (s *MSG91Service) SendMobileOTP(mobile string, otp string) (*MSG91SMSResponse, error) {

	request := MSG91SMSRequest{
		AuthKey:       s.AuthKey,
		APIURL:        s.APIURL,
		Mobile:        normalizeIndianMobile(mobile),
		OTP:           otp,
		Message:       BuildOTPMessage(otp),
		Sender:        s.Sender,
		DLTTemplateID: s.DLTTemplateID,
		PeID:          s.PeID,
		Route:         s.Route,
		Country:       s.Country,
	}
	return s.SendSMS(request)
}

func (s *MSG91Service) SendSMS(request MSG91SMSRequest) (*MSG91SMSResponse, error) {

	authKey := strings.TrimSpace(request.AuthKey)

	if authKey == "" {
		authKey = s.AuthKey
	}

	if authKey == "" {
		return nil, fmt.Errorf("MSG91 auth key is missing")
	}

	apiURL := strings.TrimSpace(request.APIURL)

	if apiURL == "" {
		apiURL = s.APIURL
	}

	if apiURL == "" {
		apiURL = defaultMsg91ApiURL
	}

	mobile := strings.TrimSpace(request.Mobile)

	if mobile == "" {
		return nil, fmt.Errorf("mobile number is required")
	}

	mobile = normalizeIndianMobile(mobile)

	otp := strings.TrimSpace(request.OTP)

	sender := strings.TrimSpace(request.Sender)

	if sender == "" {
		sender = s.Sender
	}

	if sender == "" {
		sender = defaultMsg91Sender
	}

	dltTemplateID := strings.TrimSpace(
		request.DLTTemplateID,
	)

	if dltTemplateID == "" {
		dltTemplateID = s.DLTTemplateID
	}

	if dltTemplateID == "" {
		return nil, fmt.Errorf("DLT template ID is missing")
	}

	peID := strings.TrimSpace(request.PeID)

	if peID == "" {
		peID = s.PeID
	}

	route := strings.TrimSpace(request.Route)

	if route == "" {
		route = s.Route
	}

	if route == "" {
		route = defaultMsg91Route
	}

	country := strings.TrimSpace(request.Country)

	if country == "" {
		country = s.Country
	}

	if country == "" {
		country = defaultMsg91Country
	}

	message := strings.TrimSpace(request.Message)

	if message == "" {
		message = BuildOTPMessage(otp)
	}

	// ---------------------------------------------------------
	// 2. Build URL query parameters
	// ---------------------------------------------------------

	query := url.Values{}

	query.Set("authkey", authKey)
	query.Set("mobiles", mobile)
	query.Set("message", message)
	query.Set("sender", sender)
	query.Set("route", route)
	query.Set("country", country)
	query.Set("DLT_TE_ID", dltTemplateID)

	// The principal entity id is only sent when one is configured: the legacy
	// gateway rejects an empty DLT_PE_ID rather than ignoring it.
	if peID != "" {
		query.Set("DLT_PE_ID", peID)
	}

	finalURL := apiURL + "?" + query.Encode()

	// ---------------------------------------------------------
	// 3. Create GET request
	// ---------------------------------------------------------

	req, err := http.NewRequest(
		http.MethodGet,
		finalURL,
		nil,
	)

	if err != nil {
		return nil, fmt.Errorf(
			"failed to create MSG91 request: %w",
			err,
		)
	}

	req.Header.Set(
		"Accept",
		"*/*",
	)

	// ---------------------------------------------------------
	// 4. HTTP client
	// ---------------------------------------------------------

	client := &http.Client{
		Timeout: 15 * time.Second,

		// Equivalent to CURLOPT_FOLLOWLOCATION => true
		CheckRedirect: func(
			req *http.Request,
			via []*http.Request,
		) error {
			if len(via) >= 10 {
				return fmt.Errorf(
					"too many redirects",
				)
			}

			return nil
		},
	}

	// ---------------------------------------------------------
	// 5. Send request
	// ---------------------------------------------------------

	resp, err := client.Do(req)

	if err != nil {

		// Only the transport error is kept. http.Client wraps the failure in a
		// *url.Error carrying the full URL, and this URL's query string holds
		// the auth key and the plaintext OTP - a carrier outage used to dump
		// every live OTP and the production key straight into the log.
		if urlErr, ok := err.(*url.Error); ok {

			return nil, fmt.Errorf(
				"MSG91 request to %s failed: %v",
				apiURL,
				urlErr.Err,
			)
		}

		return nil, fmt.Errorf(
			"MSG91 request failed: %v",
			err,
		)
	}

	defer resp.Body.Close()

	// ---------------------------------------------------------
	// 6. Read response
	// ---------------------------------------------------------

	responseBody, err := io.ReadAll(resp.Body)

	if err != nil {
		return nil, fmt.Errorf(
			"failed to read MSG91 response: %w",
			err,
		)
	}

	rawResponse := strings.TrimSpace(
		string(responseBody),
	)

	// ---------------------------------------------------------
	// 7. HTTP error handling
	// ---------------------------------------------------------

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {

		return &MSG91SMSResponse{
				Success:     false,
				StatusCode:  resp.StatusCode,
				Message:     "MSG91 SMS request failed",
				RawResponse: rawResponse,
			}, fmt.Errorf(
				"MSG91 HTTP %d: %s",
				resp.StatusCode,
				rawResponse,
			)
	}

	// ---------------------------------------------------------
	// 8. Gateway response handling
	// ---------------------------------------------------------

	success := isMSG91SuccessResponse(rawResponse)

	return &MSG91SMSResponse{
		Success:     success,
		StatusCode:  resp.StatusCode,
		Message:     rawResponse,
		RawResponse: rawResponse,
	}, nil
}

// BuildOTPMessage creates the SMS message dynamically.
//
// Example:
//
// Your OTP for AstroEye is 123456. Please do not share this OTP
// with anyone. - NOVAEDGESYSTEM PRIVATE LIMITED

func BuildOTPMessage(otp string) string {
	return fmt.Sprintf("Your OTP for AstroEye is %s. Please do not share this OTP with anyone. - NOVAEDGESYSTEM PRIVATE LIMITED", otp)
}

func normalizeIndianMobile(mobile string) string {

	mobile = strings.TrimSpace(mobile)

	mobile = strings.ReplaceAll(
		mobile,
		" ",
		"",
	)

	mobile = strings.TrimPrefix(
		mobile,
		"+",
	)

	mobile = strings.TrimPrefix(
		mobile,
		"0",
	)

	if !strings.HasPrefix(
		mobile,
		"91",
	) {
		mobile = "91" + mobile
	}
	return mobile
}

// isMSG91SuccessResponse checks common success responses
// returned by the legacy SMS gateway.
//
// The exact response can vary depending on the gateway.
func isMSG91SuccessResponse(response string) bool {

	response = strings.ToLower(
		strings.TrimSpace(response),
	)

	if response == "" {
		return false
	}

	// Common success indicators.
	successKeywords := []string{
		"success",
		"sent",
		"accepted",
		"submitted",
		"successfully",
	}

	for _, keyword := range successKeywords {

		if strings.Contains(
			response,
			keyword,
		) {
			return true
		}
	}

	return false
}
