package devicecode

import (
	"context"
	"errors"
	"testing"

	"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/infrastructure/browser_mock"
	"github.com/int128/kubelogin/mocks/github.com/int128/kubelogin/pkg/oidc/client_mock"
	"github.com/int128/kubelogin/pkg/oidc"
	"github.com/int128/kubelogin/pkg/testing/logger"
	"github.com/int128/oauth2dev"
	"github.com/stretchr/testify/mock"
)

func TestDeviceCode(t *testing.T) {
	ctx := context.TODO()

	t.Run("Authorization error", func(t *testing.T) {
		mockClient := client_mock.NewMockInterface(t)
		dc := &DeviceCode{
			Browser: browser_mock.NewMockInterface(t),
			Logger:  logger.New(t),
		}
		errTest := errors.New("test error")
		mockClient.EXPECT().GetDeviceAuthorization(ctx).Return(nil, errTest).Once()
		_, err := dc.Do(ctx, &Option{}, mockClient)
		if !errors.Is(err, errTest) {
			t.Errorf("returned error is not the test error: %v", err)
		}
	})

	t.Run("Server returns verification_uri_complete", func(t *testing.T) {
		mockBrowser := browser_mock.NewMockInterface(t)
		mockClient := client_mock.NewMockInterface(t)
		dc := &DeviceCode{
			Browser: mockBrowser,
			Logger:  logger.New(t),
		}
		mockResponse := &oauth2dev.AuthorizationResponse{
			DeviceCode:              "device-code-1",
			VerificationURIComplete: "https://example.com/verificationComplete?code=code123",
			ExpiresIn:               2,
			Interval:                1,
		}
		mockClient.EXPECT().GetDeviceAuthorization(ctx).Return(&oauth2dev.AuthorizationResponse{
			Interval:                1,
			ExpiresIn:               2,
			VerificationURIComplete: "https://example.com/verificationComplete?code=code123",
			DeviceCode:              "device-code-1",
		}, nil).Once()
		mockBrowser.EXPECT().Open("https://example.com/verificationComplete?code=code123").Return(nil).Once()
		mockClient.EXPECT().ExchangeDeviceCode(mock.Anything, mockResponse).Return(&oidc.TokenSet{
			IDToken: "test-id-token",
		}, nil).Once()
		ts, err := dc.Do(ctx, &Option{}, mockClient)
		if err != nil {
			t.Errorf("returned unexpected error: %v", err)
		}
		if ts.IDToken != "test-id-token" {
			t.Errorf("wrong returned tokenset: %v", err)
		}
	})

	t.Run("Server returns verification_uri", func(t *testing.T) {
		mockBrowser := browser_mock.NewMockInterface(t)
		mockClient := client_mock.NewMockInterface(t)
		dc := &DeviceCode{
			Browser: mockBrowser,
			Logger:  logger.New(t),
		}
		mockResponseWithoutComplete := &oauth2dev.AuthorizationResponse{
			DeviceCode:      "device-code-1",
			VerificationURI: "https://example.com/verificationComplete",
			ExpiresIn:       2,
			Interval:        1,
		}
		mockClient.EXPECT().GetDeviceAuthorization(ctx).Return(&oauth2dev.AuthorizationResponse{
			Interval:        1,
			ExpiresIn:       2,
			VerificationURI: "https://example.com/verificationComplete",
			DeviceCode:      "device-code-1",
		}, nil).Once()
		mockBrowser.EXPECT().Open("https://example.com/verificationComplete").Return(nil).Once()
		mockClient.EXPECT().ExchangeDeviceCode(mock.Anything, mockResponseWithoutComplete).Return(&oidc.TokenSet{
			IDToken: "test-id-token",
		}, nil).Once()
		ts, err := dc.Do(ctx, &Option{}, mockClient)
		if err != nil {
			t.Errorf("returned unexpected error: %v", err)
		}
		if ts.IDToken != "test-id-token" {
			t.Errorf("wrong returned tokenset: %v", err)
		}
	})

	t.Run("Server returns verification_url", func(t *testing.T) {
		mockBrowser := browser_mock.NewMockInterface(t)
		mockClient := client_mock.NewMockInterface(t)
		dc := &DeviceCode{
			Browser: mockBrowser,
			Logger:  logger.New(t),
		}
		mockResponse := &oauth2dev.AuthorizationResponse{
			DeviceCode:      "device-code-1",
			VerificationURL: "https://example.com/verificationCompleteURL",
			ExpiresIn:       2,
			Interval:        1,
		}
		mockClient.EXPECT().GetDeviceAuthorization(ctx).Return(mockResponse, nil).Once()
		mockBrowser.EXPECT().Open("https://example.com/verificationCompleteURL").Return(nil).Once()
		mockClient.EXPECT().ExchangeDeviceCode(mock.Anything, mockResponse).Return(&oidc.TokenSet{
			IDToken: "test-id-token",
		}, nil).Once()
		ts, err := dc.Do(ctx, &Option{}, mockClient)
		if err != nil {
			t.Errorf("returned unexpected error: %v", err)
		}
		if ts.IDToken != "test-id-token" {
			t.Errorf("wrong returned tokenset: %v", err)
		}
	})

	t.Run("Error when exchanging the device code", func(t *testing.T) {
		mockBrowser := browser_mock.NewMockInterface(t)
		mockClient := client_mock.NewMockInterface(t)
		dc := &DeviceCode{
			Browser: mockBrowser,
			Logger:  logger.New(t),
		}
		mockResponse := &oauth2dev.AuthorizationResponse{
			DeviceCode:              "device-code-1",
			VerificationURIComplete: "https://example.com/verificationComplete?code=code123",
			ExpiresIn:               2,
			Interval:                1,
		}
		mockClient.EXPECT().GetDeviceAuthorization(ctx).Return(&oauth2dev.AuthorizationResponse{
			Interval:                1,
			ExpiresIn:               2,
			VerificationURIComplete: "https://example.com/verificationComplete?code=code123",
			DeviceCode:              "device-code-1",
		}, nil).Once()
		mockBrowser.EXPECT().Open("https://example.com/verificationComplete?code=code123").Return(nil).Once()
		mockClient.EXPECT().ExchangeDeviceCode(mock.Anything, mockResponse).Return(nil, errors.New("test error")).Once()
		_, err := dc.Do(ctx, &Option{}, mockClient)
		if err == nil {
			t.Errorf("did not return error: %v", err)
		}
	})
}

func TestDeviceCode_openURL(t *testing.T) {
	ctx := context.TODO()
	const url = "https://example.com"
	var testError = errors.New("test error")

	t.Run("Continue if error opening the browser", func(t *testing.T) {
		browserMock := browser_mock.NewMockInterface(t)
		deviceCode := &DeviceCode{
			Browser: browserMock,
			Logger:  logger.New(t),
		}
		browserMock.EXPECT().Open(url).Return(testError).Once()
		deviceCode.openURL(ctx, nil, url)
	})

	t.Run("SkipOpenBrowser is set", func(t *testing.T) {
		browserMock := browser_mock.NewMockInterface(t)
		deviceCode := &DeviceCode{
			Browser: browserMock,
			Logger:  logger.New(t),
		}
		deviceCode.openURL(ctx, &Option{SkipOpenBrowser: true}, url)
	})

	t.Run("BrowserCommand is set", func(t *testing.T) {
		browserMock := browser_mock.NewMockInterface(t)
		deviceCode := &DeviceCode{
			Browser: browserMock,
			Logger:  logger.New(t),
		}
		browserMock.EXPECT().OpenCommand(ctx, url, "test-command").Return(testError).Once()
		deviceCode.openURL(ctx, &Option{BrowserCommand: "test-command"}, url)
	})
}
