// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package describe

import (
	"errors"
	"fmt"
	"testing"
	"time"

	"github.com/dustin/go-humanize"

	"github.com/aws/copilot-cli/internal/pkg/aws/codepipeline"
	"github.com/aws/copilot-cli/internal/pkg/deploy"
	"github.com/aws/copilot-cli/internal/pkg/describe/mocks"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/require"
)

type pipelineStatusDescriberMocks struct {
	pipelineStateGetter *mocks.MockpipelineStateGetter
}

var mockParsedTime = func() time.Time {
	t, _ := time.Parse(time.RFC3339, "2020-02-02T15:04:05+00:00")
	return t
}
var mockPipelineState = &codepipeline.PipelineState{
	PipelineName: pipelineResourceName,
	StageStates: []*codepipeline.StageState{
		{
			StageName: "Source",
		},
		{
			StageName: "Build",
			Actions: []codepipeline.StageAction{
				{
					Name:   "action1",
					Status: "Failed",
				},
				{
					Name:   "action2",
					Status: "InProgress",
				},
				{
					Name:   "action3",
					Status: "Succeeded",
				},
			},
			Transition: "ENABLED",
		},
		{
			StageName: "DeployTo-test",
			Actions: []codepipeline.StageAction{
				{
					Name:   "action1",
					Status: "Succeeded",
				},
			},
			Transition: "DISABLED",
		},
		{
			StageName: "DeployTo-prod",
			Actions: []codepipeline.StageAction{
				{
					Name:   "action1",
					Status: "Succeeded",
				},
				{
					Name:   "TestCommands",
					Status: "Failed",
				},
			},
		},
	},
	UpdatedAt: mockParsedTime(),
}

func TestPipelineStatusDescriber_Describe(t *testing.T) {
	mockError := errors.New("some error")

	testCases := map[string]struct {
		setupMocks func(m pipelineStatusDescriberMocks)

		expectedError  error
		expectedOutput *PipelineStatus
	}{
		"wraps GetPipelineState error": {
			setupMocks: func(m pipelineStatusDescriberMocks) {
				m.pipelineStateGetter.EXPECT().GetPipelineState(pipelineResourceName).Return(nil, mockError)
			},
			expectedError:  fmt.Errorf("get pipeline status: %w", mockError),
			expectedOutput: nil,
		},
		"success": {
			setupMocks: func(m pipelineStatusDescriberMocks) {
				m.pipelineStateGetter.EXPECT().GetPipelineState(pipelineResourceName).Return(mockPipelineState, nil)
			},
			expectedError: nil,
			expectedOutput: &PipelineStatus{
				Name:          pipelineName,
				PipelineState: *mockPipelineState,
			},
		},
	}
	for name, tc := range testCases {
		t.Run(name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockPipelineStateGetter := mocks.NewMockpipelineStateGetter(ctrl)

			mocks := pipelineStatusDescriberMocks{
				pipelineStateGetter: mockPipelineStateGetter,
			}
			tc.setupMocks(mocks)

			mockDeployedPipeline := deploy.Pipeline{
				Name:         pipelineName,
				ResourceName: pipelineResourceName,
				AppName:      "mockApp",
				IsLegacy:     false,
			}

			describer := &PipelineStatusDescriber{
				pipeline:    mockDeployedPipeline,
				pipelineSvc: mockPipelineStateGetter,
			}

			// WHEN
			pipelineStatus, err := describer.Describe()

			// THEN
			if tc.expectedError != nil {
				require.EqualError(t, err, tc.expectedError.Error())
			} else {
				require.NoError(t, err)
				require.Equal(t, tc.expectedOutput, pipelineStatus, "expected output content match")
			}
		})
	}
}

func TestPipelineStatusDescriber_String(t *testing.T) {
	oldHumanize := humanizeTime
	humanizeTime = func(then time.Time) string {
		now, _ := time.Parse(time.RFC3339, "2020-06-19T00:00:00+00:00")
		return humanize.RelTime(then, now, "ago", "from now")
	}
	defer func() {
		humanizeTime = oldHumanize
	}()
	testCases := map[string]struct {
		testPipelineStatus  *PipelineStatus
		expectedHumanString string
		expectedJSONString  string
	}{
		"correct output with correct aggregate statuses": {
			testPipelineStatus: &PipelineStatus{
				Name:          pipelineName,
				PipelineState: *mockPipelineState},
			expectedHumanString: `Pipeline Status

Stage             Transition  Status
-----             ----------  ------
Source              -           -
Build             ENABLED     InProgress
├── action1                   Failed
├── action2                   InProgress
└── action3                   Succeeded
DeployTo-test     DISABLED    Succeeded
└── action1                   Succeeded
DeployTo-prod       -         Failed
├── action1                   Succeeded
└── TestCommands              Failed

Last Deployment

  Updated At  4 months ago
`,
			expectedJSONString: "{\"name\":\"pipeline-dinder-badgoose-repo\",\"pipelineName\":\"pipeline-dinder-badgoose-repo-RANDOMSTRING\",\"stageStates\":[{\"stageName\":\"Source\",\"transition\":\"\"},{\"stageName\":\"Build\",\"actions\":[{\"name\":\"action1\",\"status\":\"Failed\"},{\"name\":\"action2\",\"status\":\"InProgress\"},{\"name\":\"action3\",\"status\":\"Succeeded\"}],\"transition\":\"ENABLED\"},{\"stageName\":\"DeployTo-test\",\"actions\":[{\"name\":\"action1\",\"status\":\"Succeeded\"}],\"transition\":\"DISABLED\"},{\"stageName\":\"DeployTo-prod\",\"actions\":[{\"name\":\"action1\",\"status\":\"Succeeded\"},{\"name\":\"TestCommands\",\"status\":\"Failed\"}],\"transition\":\"\"}],\"updatedAt\":\"2020-02-02T15:04:05Z\"}\n",
		},
	}
	for _, tc := range testCases {
		human := tc.testPipelineStatus.HumanString()
		json, _ := tc.testPipelineStatus.JSONString()

		require.Equal(t, tc.expectedHumanString, human, "expected human output to match")
		require.Equal(t, tc.expectedJSONString, json, "expected JSON output to match")
	}
}
