package sms

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

type roundTripFunc func(*http.Request) (*http.Response, error)

func (fn roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return fn(request)
}

func TestNewTwilioRequiresCredentials(t *testing.T) {
	t.Parallel()

	_, err := NewTwilio(TwilioOptions{})
	require.EqualError(t, err, "twilio account SID is required")

	_, err = NewTwilio(TwilioOptions{
		AccountSID: "AC123",
	})
	require.EqualError(t, err, "twilio auth token is required")
}

func TestNewTwilioRequiresExactlyOneSenderIdentity(t *testing.T) {
	t.Parallel()

	_, err := NewTwilio(TwilioOptions{
		AccountSID: "AC123",
		AuthToken:  "secret",
	})
	require.EqualError(t, err, "twilio from number or messaging service SID is required")

	_, err = NewTwilio(TwilioOptions{
		AccountSID:          "AC123",
		AuthToken:           "secret",
		FromNumber:          "+15557654321",
		MessagingServiceSID: "MG123",
	})
	require.EqualError(t, err, "twilio from number and messaging service SID are mutually exclusive")
}

func TestTwilioSenderSendsVerificationCodeWithFromNumber(t *testing.T) {
	t.Parallel()

	sender, err := NewTwilio(TwilioOptions{
		AccountSID: "AC123",
		AuthToken:  "secret",
		FromNumber: "+15557654321",
		APIBaseURL: "https://twilio.example",
		HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			t.Helper()

			require.Equal(t, http.MethodPost, request.Method)
			require.Equal(t, "https://twilio.example/2010-04-01/Accounts/AC123/Messages.json", request.URL.String())

			username, password, ok := request.BasicAuth()
			require.True(t, ok)
			require.Equal(t, "AC123", username)
			require.Equal(t, "secret", password)
			require.Equal(t, "application/x-www-form-urlencoded", request.Header.Get("Content-Type"))

			body, readErr := io.ReadAll(request.Body)
			require.NoError(t, readErr)

			form, parseErr := url.ParseQuery(string(body))
			require.NoError(t, parseErr)
			require.Equal(t, "+15551234567", form.Get("To"))
			require.Equal(t, "+15557654321", form.Get("From"))
			require.Equal(t, "", form.Get("MessagingServiceSid"))
			require.Equal(t, "Your AgentTeam verification code is 123456", form.Get("Body"))

			return &http.Response{
				StatusCode: http.StatusCreated,
				Body:       io.NopCloser(strings.NewReader(`{"sid":"SM123","status":"queued"}`)),
				Header:     make(http.Header),
			}, nil
		})},
	})
	require.NoError(t, err)

	receipt, err := sender.Send(context.Background(), "+15551234567", "123456")
	require.NoError(t, err)
	require.Equal(t, Receipt{
		Provider:          ProviderTwilio,
		SenderIdentity:    "+15557654321",
		ProviderMessageID: "SM123",
		ProviderStatus:    "queued",
	}, receipt)
}

func TestTwilioSenderSendsVerificationCodeWithMessagingServiceSID(t *testing.T) {
	t.Parallel()

	sender, err := NewTwilio(TwilioOptions{
		AccountSID:          "AC123",
		AuthToken:           "secret",
		MessagingServiceSID: "MG123",
		APIBaseURL:          "https://twilio.example",
		HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			t.Helper()

			body, readErr := io.ReadAll(request.Body)
			require.NoError(t, readErr)

			form, parseErr := url.ParseQuery(string(body))
			require.NoError(t, parseErr)
			require.Equal(t, "+15551234567", form.Get("To"))
			require.Equal(t, "", form.Get("From"))
			require.Equal(t, "MG123", form.Get("MessagingServiceSid"))
			require.Equal(t, "Your AgentTeam verification code is 123456", form.Get("Body"))

			return &http.Response{
				StatusCode: http.StatusCreated,
				Body:       io.NopCloser(strings.NewReader(`{"sid":"SM456","status":"accepted"}`)),
				Header:     make(http.Header),
			}, nil
		})},
	})
	require.NoError(t, err)

	receipt, err := sender.Send(context.Background(), "+15551234567", "123456")
	require.NoError(t, err)
	require.Equal(t, Receipt{
		Provider:          ProviderTwilio,
		SenderIdentity:    "MG123",
		ProviderMessageID: "SM456",
		ProviderStatus:    "accepted",
	}, receipt)
}

func TestTwilioSenderReturnsProviderError(t *testing.T) {
	t.Parallel()

	sender, err := NewTwilio(TwilioOptions{
		AccountSID: "AC123",
		AuthToken:  "secret",
		FromNumber: "+15557654321",
		APIBaseURL: "https://twilio.example",
		HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			t.Helper()
			return &http.Response{
				StatusCode: http.StatusBadRequest,
				Body:       io.NopCloser(strings.NewReader(`{"code":21211,"message":"The 'To' number is not a valid phone number.","more_info":"https://www.twilio.com/docs/errors/21211"}`)),
				Header:     make(http.Header),
			}, nil
		})},
	})
	require.NoError(t, err)

	receipt, err := sender.Send(context.Background(), "+15551234567", "123456")
	require.Equal(t, Receipt{Provider: ProviderTwilio, SenderIdentity: "+15557654321"}, receipt)
	require.EqualError(t, err, `send twilio verification SMS: status 400 code 21211: The 'To' number is not a valid phone number. (https://www.twilio.com/docs/errors/21211)`)
}
