package tmobile

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/pkg/errors"
)

func TestSMSClient_Start(t *testing.T) {
	t.Skip()
	clSucc := MockClientAccTokResSucc{wasSet: make(chan string, 1)}
	smsc := NewSMSClient(&clSucc, nil)

	ctx, cancel := context.WithCancel(context.Background())

	go smsc.Start(ctx)

	t.Log("Waiting for FIRST access token to be set")

	select {
	case gotAt := <-clSucc.wasSet:
		if gotAt != fmt.Sprintf("%s%d", mockAccessTokenResponse.AccessToken, 0) {
			t.Errorf("expected access token %s, got %s", mockAccessTokenResponse.AccessToken, gotAt)

			close(clSucc.wasSet)
			cancel()

			return
		}

		t.Log("First access token set")
	case <-time.After(time.Second):
		t.Error("Access token was not set, cancelling test")

		close(clSucc.wasSet)
		cancel()

		return
	}

	time.Sleep(time.Second)

	t.Log("Waiting for REFRESHED access token to be set")

	select {
	case gotAt := <-clSucc.wasSet:
		if gotAt != fmt.Sprintf("%s%d", mockAccessTokenResponse.AccessToken, 1) {
			t.Errorf("expected access token %s, got %s", mockAccessTokenResponse.AccessToken, gotAt)

			cancel()
			close(clSucc.wasSet)

			return
		}

		t.Log("Refreshed access token set")
	case <-time.After(time.Second):
		t.Error("Access token was not set, cancelling test")

		cancel()
		close(clSucc.wasSet)

		return
	}

	t.Log("Waiting for CANCELLATION")
	cancel()

	doneChan := ctx.Done()

	if _, ok := <-doneChan; ok {
		t.Error("Context was not cancelled")

		close(clSucc.wasSet)

		return
	} else {
		t.Log("Context was cancelled")
	}

	time.Sleep(time.Second)

	if len(clSucc.wasSet) != 0 {
		t.Error("Access token was set after cancellation")

		close(clSucc.wasSet)

		return
	}

	t.Log("Success test finished")

	clFail := MockClientAccTokResFail{wasSet: make(chan struct{}, 3)}
	smsc = NewSMSClient(&clFail, nil)

	ctx, cancel = context.WithCancel(context.Background())
	go smsc.Start(ctx)

	time.Sleep(time.Millisecond * 100)

	if len(clFail.wasSet) <= 2 {
		t.Errorf("Access token was not set after failure: len(chan) = %d", len(clFail.wasSet))
		cancel()
		close(clFail.wasSet)

		return
	}

	cancel()

	t.Log("Test finished")
}

var mockSmsRequest = &SendSMSRequest{
	ICCID:       "12345678901234567890",
	MessageText: "Hello world",
	await:       true,
}

var mockCtx = context.Background()

func TestSMSClient_SendSMS(t *testing.T) {
	t.Skip()
	cl := MockClientSendSMSResSucc{wasSet: make(chan SmsDetailsStatus, 3)}
	smsc := NewSMSClient(&cl, nil)

	t.Log("sending succ SMS")
	r, err := smsc.SendSMS(mockCtx, mockSmsRequest)
	if err != nil {
		t.Errorf("SendSMS failed: %v", err)

		close(cl.wasSet)

		return
	}

	if r.Status != Delivered {
		t.Errorf("SendSMS returned status %s, expected %s", r.Status, Delivered)

		close(cl.wasSet)

		return
	}

	t.Log("getting succ SMS statuses")
	for i := 0; i < 3; i++ {
		select {
		case status := <-cl.wasSet:
			t.Logf("sms status was set: %s", status)
		case <-time.After(time.Second):
			t.Error("sms was not set")
			return
		}
	}
}

func TestSMSClient_SendSMS_FailOnSend(t *testing.T) {
	t.Skip()
	cl := MockClientSendSMSResFail{wasSet: make(chan struct{}, 1)}
	smsc := NewSMSClient(&cl, nil)

	t.Log("sending fail SMS")
	out, err := smsc.SendSMS(mockCtx, mockSmsRequest)
	if !strings.HasPrefix(err.Error(), "failed to send SMS") {
		t.Error("SendSMS should have failed")

		close(cl.wasSet)

		return
	}

	if out != nil {
		t.Error("SendSMS should have returned nil")

		close(cl.wasSet)

		return
	}

	t.Log("getting SendSMS failure")
	select {
	case <-cl.wasSet:
		t.Log("SendSMS was called")
	case <-time.After(time.Second):
		t.Error("SendSMS was not called")
		return
	}
}

func TestSMSClient_SendSMSFailOnDetailsStatus(t *testing.T) {
	t.Skip()
	for _, curType := range smsStatusFailTypes {
		cl := MockClientSendSMSDetailsFailWithBadStatus{wasSet: make(chan SmsDetailsStatus, len(curType)), toFail: curType}
		smsc := NewSMSClient(&cl, nil)

		t.Logf("sending fail SMS with type %s", curType[len(curType)-1])

		r, err := smsc.SendSMS(mockCtx, mockSmsRequest)
		if err == nil {
			t.Errorf("SendDetails should have failed: %v", err)

			close(cl.wasSet)

			return
		}

		if r == nil {
			t.Error("SendDetails should have returned non-nil")

			close(cl.wasSet)

			return
		}

		t.Log("getting succ SMS statuses")
		for i := 0; i < len(curType); i++ {
			select {
			case status := <-cl.wasSet:
				t.Logf("sms status was set: %s", status)
			case <-time.After(time.Second):
				t.Error("sms was not set")
				return
			}
		}
	}
}

func TestSMSClient_SendSMSFailOnDetails(t *testing.T) {
	// TODO fix the timing of this test, skipping for now
	t.Skip()

	cl := &MockClientSendSMSDetailsFailRes{wasSet: make(chan struct{}, 1)}
	smsc := NewSMSClient(cl, nil)

	r, err := smsc.SendSMS(mockCtx, mockSmsRequest)
	if !strings.HasPrefix(err.Error(), "failed to get sms details") {
		t.Errorf("SendDetails should have failed: %v", err)

		close(cl.wasSet)

		return
	}

	if r != nil {
		t.Errorf("SendDetails should have returned nil: %v", r)

		close(cl.wasSet)

		return
	}

	select {
	case <-cl.wasSet:
		t.Log("SendDetails was called")
	case <-time.After(time.Second):
		t.Error("SendDetails was not called")
	}
}

func TestNewSMSClient_SendSMSAwaitExpire(t *testing.T) {
	t.Skip()
	cl := &MockClientSendSMSExpire{}
	smsc := NewSMSClient(cl, nil)

	r, err := smsc.SendSMS(mockCtx, mockSmsRequest)
	if !errors.Is(err, ErrTimeoutSendingMessage) {
		t.Errorf("SendSMS should have failed: %v", err)

		return
	}

	if r != nil {
		t.Errorf("SendSMS should have returned nil: %v", r)

		return
	}

}

type MockWrapperFail struct {
	SMSClientWrapper
	wasSet chan struct{}
}

func (mw *MockWrapperFail) SendSMS(ctx context.Context, req *SendSMSRequest) (*SMSDetailsResponse, error) {
	mw.wasSet <- struct{}{}

	return nil, ErrTimeoutSendingMessage
}

type MockWrapper struct {
	SMSClientWrapper
	wasSet chan struct{}
}

func (mw *MockWrapper) SendSMS(ctx context.Context, req *SendSMSRequest) (*SMSDetailsResponse, error) {
	mw.wasSet <- struct{}{}

	return mockSmsDetailsResponse, nil
}

func TestWrapperIntegrationTest(t *testing.T) {
	t.Skip()
	tg, err := InitTokenGen(
		"PRIVATE KEY",
		"ID",
		"PASSWORD",
	)
	if err != nil {
		t.Error(err)
	}

	tmobc, err := NewTMobileClient(
		Endpoint,
		tg, time.Minute*2)

	client := NewSMSClient(tmobc, nil)

	iccids := []string{"8901882000784161105", "8901882000784163135", "8901882000784161071",
		"8901882000784164976",
		"8901882000784163135",
		"8901882000787584451",
		"8901882000784166427",
		"8901882000784163671",
		"8901882000784163945",
		"8901882000784167342",
		"8901882000784166625"}

	wg := sync.WaitGroup{}
	// Running test 10 times in a row to see if we get any failures
	for _, iccid := range iccids {
		wg.Add(1)
		go func(iccid string) {
			msg := SendSMSRequest{
				ICCID:       iccid,
				MessageText: "newer_test",
				await:       true,
			}
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*45)
			defer cancel()
			_, err := client.SendSMS(ctx, &msg)
			if err != nil {
				t.Error(err)
			}
			wg.Done()
		}(iccid)
	}
	wg.Wait()
}

// Want to benchmark and record the memory and cpu usage of having different ways to check if the sms was delivered
func BenchmarkSMSWrapper(b *testing.B) {
	sim := newTMboileSimulator()

	wrap := NewSMSClient(&sim, nil)
	tt := sync.WaitGroup{}
	for x := 0; x < 1000; x++ {
		tt.Add(1)
		go func() {
			wrap.SendSMS(context.Background(), &SendSMSRequest{
				MessageText: ".",
				await:       true,
			})
			tt.Done()
		}()
	}
	tt.Wait()
}
