package sms

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

const defaultTwilioAPIBaseURL = "https://api.twilio.com"

type TwilioOptions struct {
	AccountSID          string
	AuthToken           string
	FromNumber          string
	MessagingServiceSID string
	APIBaseURL          string
	HTTPClient          *http.Client
}

type twilioSender struct {
	accountSID          string
	authToken           string
	fromNumber          string
	messagingServiceSID string
	apiBaseURL          string
	httpClient          *http.Client
}

func NewTwilio(options TwilioOptions) (Sender, error) {
	accountSID := strings.TrimSpace(options.AccountSID)
	if accountSID == "" {
		return nil, fmt.Errorf("twilio account SID is required")
	}
	authToken := strings.TrimSpace(options.AuthToken)
	if authToken == "" {
		return nil, fmt.Errorf("twilio auth token is required")
	}

	fromNumber := strings.TrimSpace(options.FromNumber)
	messagingServiceSID := strings.TrimSpace(options.MessagingServiceSID)
	if fromNumber == "" && messagingServiceSID == "" {
		return nil, fmt.Errorf("twilio from number or messaging service SID is required")
	}
	if fromNumber != "" && messagingServiceSID != "" {
		return nil, fmt.Errorf("twilio from number and messaging service SID are mutually exclusive")
	}

	apiBaseURL := strings.TrimRight(strings.TrimSpace(options.APIBaseURL), "/")
	if apiBaseURL == "" {
		apiBaseURL = defaultTwilioAPIBaseURL
	}
	parsedAPIBaseURL, err := url.Parse(apiBaseURL)
	if err != nil {
		return nil, fmt.Errorf("parse twilio API base URL: %w", err)
	}
	if !parsedAPIBaseURL.IsAbs() {
		return nil, fmt.Errorf("twilio API base URL must be absolute")
	}

	httpClient := options.HTTPClient
	if httpClient == nil {
		httpClient = &http.Client{Timeout: 10 * time.Second}
	}

	return &twilioSender{
		accountSID:          accountSID,
		authToken:           authToken,
		fromNumber:          fromNumber,
		messagingServiceSID: messagingServiceSID,
		apiBaseURL:          apiBaseURL,
		httpClient:          httpClient,
	}, nil
}

func (sender *twilioSender) Send(ctx context.Context, mobile string, code string) (Receipt, error) {
	form := url.Values{}
	form.Set("To", mobile)
	form.Set("Body", fmt.Sprintf("Your AgentTeam verification code is %s", code))
	if sender.fromNumber != "" {
		form.Set("From", sender.fromNumber)
	} else {
		form.Set("MessagingServiceSid", sender.messagingServiceSID)
	}

	endpoint := fmt.Sprintf("%s/2010-04-01/Accounts/%s/Messages.json", sender.apiBaseURL, url.PathEscape(sender.accountSID))
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(form.Encode()))
	if err != nil {
		return Receipt{Provider: ProviderTwilio, SenderIdentity: sender.senderIdentity()}, fmt.Errorf("build twilio verification request: %w", err)
	}
	request.SetBasicAuth(sender.accountSID, sender.authToken)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	response, err := sender.httpClient.Do(request)
	if err != nil {
		return Receipt{Provider: ProviderTwilio, SenderIdentity: sender.senderIdentity()}, fmt.Errorf("send twilio verification SMS: %w", err)
	}
	defer response.Body.Close()

	receipt := Receipt{
		Provider:       ProviderTwilio,
		SenderIdentity: sender.senderIdentity(),
	}

	body, readErr := io.ReadAll(io.LimitReader(response.Body, 4096))
	if response.StatusCode/100 == 2 {
		if readErr == nil {
			receipt.ProviderMessageID, receipt.ProviderStatus = parseTwilioAcceptedResponse(body)
		}
		return receipt, nil
	}

	if readErr != nil {
		return receipt, fmt.Errorf("send twilio verification SMS: status %d", response.StatusCode)
	}
	return receipt, formatTwilioSendError(response.StatusCode, body)
}

func (sender *twilioSender) senderIdentity() string {
	if sender.fromNumber != "" {
		return sender.fromNumber
	}
	return sender.messagingServiceSID
}

func parseTwilioAcceptedResponse(body []byte) (string, string) {
	if len(body) == 0 {
		return "", ""
	}

	var payload struct {
		SID    string `json:"sid"`
		Status string `json:"status"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return "", ""
	}

	return strings.TrimSpace(payload.SID), strings.TrimSpace(payload.Status)
}

func formatTwilioSendError(statusCode int, body []byte) error {
	message := strings.TrimSpace(string(body))
	if message == "" {
		return fmt.Errorf("send twilio verification SMS: status %d", statusCode)
	}

	var payload struct {
		Code     int    `json:"code"`
		Message  string `json:"message"`
		MoreInfo string `json:"more_info"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return fmt.Errorf("send twilio verification SMS: status %d: %s", statusCode, message)
	}

	providerMessage := strings.TrimSpace(payload.Message)
	if providerMessage == "" {
		return fmt.Errorf("send twilio verification SMS: status %d: %s", statusCode, message)
	}

	codePrefix := ""
	if payload.Code != 0 {
		codePrefix = fmt.Sprintf(" code %d", payload.Code)
	}

	moreInfo := strings.TrimSpace(payload.MoreInfo)
	if moreInfo != "" {
		return fmt.Errorf("send twilio verification SMS: status %d%s: %s (%s)", statusCode, codePrefix, providerMessage, moreInfo)
	}
	return fmt.Errorf("send twilio verification SMS: status %d%s: %s", statusCode, codePrefix, providerMessage)
}
