package analyser

import (
	"encoding/json"
	"io/ioutil"
	"testing"

	"github.com/snyk/driftctl/pkg/filter"
	"github.com/stretchr/testify/mock"

	"github.com/stretchr/testify/assert"

	testresource "github.com/snyk/driftctl/test/resource"

	"github.com/snyk/driftctl/test/goldenfile"

	"github.com/snyk/driftctl/pkg/alerter"
	"github.com/snyk/driftctl/pkg/resource"
	"github.com/snyk/driftctl/pkg/resource/aws"

	"github.com/r3labs/diff/v2"
)

func TestAnalyze(t *testing.T) {
	cases := []struct {
		name         string
		iac          []*resource.Resource
		ignoredRes   []*resource.Resource
		cloud        []*resource.Resource
		ignoredDrift []struct {
			res  *resource.Resource
			path []string
		}
		alerts     alerter.Alerts
		expected   Analysis
		hasDrifted bool
	}{
		{
			name:     "TestNilValues", // Cover division by zero case
			iac:      nil,
			cloud:    nil,
			expected: Analysis{},
		},
		{
			name:     "TestNothingToCompare", // Cover division by zero case
			iac:      []*resource.Resource{},
			cloud:    []*resource.Resource{},
			expected: Analysis{},
		},
		{
			name: "TestIgnoreFromCoverageIacNotInCloud",
			iac: []*resource.Resource{
				{
					Id: "foobar",
				},
			},
			cloud: []*resource.Resource{},
			expected: Analysis{
				summary: Summary{
					TotalResources: 1,
					TotalDeleted:   1,
				},
				deleted: []*resource.Resource{
					{
						Id: "foobar",
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "TestResourceIgnoredDeleted",
			iac: []*resource.Resource{
				{
					Id: "foobar",
				},
			},
			ignoredRes: []*resource.Resource{
				{
					Id: "foobar",
				},
			},
			cloud: []*resource.Resource{},
			expected: Analysis{
				summary: Summary{
					TotalResources: 0,
					TotalDeleted:   0,
				},
			},
			hasDrifted: false,
		},
		{
			name: "Test100PercentCoverage with ignore",
			iac: []*resource.Resource{
				{
					Id: "foobar",
				},
				{
					Id: "foobar2",
				},
			},
			ignoredRes: []*resource.Resource{
				{
					Id: "foobar2",
				},
			},
			cloud: []*resource.Resource{
				{
					Id: "foobar",
				},
				{
					Id: "foobar2",
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id: "foobar",
					},
				},
				summary: Summary{
					TotalManaged:   1,
					TotalResources: 1,
				},
			},
		},
		{
			name: "Test100PercentCoverage",
			iac: []*resource.Resource{
				{
					Id: "foobar",
				},
			},
			cloud: []*resource.Resource{
				{
					Id: "foobar",
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id: "foobar",
					},
				},
				summary: Summary{
					TotalManaged:   1,
					TotalResources: 1,
				},
			},
		},
		{
			name: "TestUnmanagedResource",
			iac:  []*resource.Resource{},
			cloud: []*resource.Resource{
				{
					Id: "foobar",
				},
			},
			expected: Analysis{
				summary: Summary{
					TotalResources: 1,
					TotalUnmanaged: 1,
				},
				unmanaged: []*resource.Resource{
					{
						Id: "foobar",
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "TestDiff",
			iac: []*resource.Resource{
				{
					Id:   "foobar",
					Type: aws.AwsAmiResourceType,
					Attrs: &resource.Attributes{
						"architecture": "foobar",
						"arn":          "barfoo",
						"ebs_block_device": []map[string]interface{}{
							{
								"volume_type": "bar",
								"volume_size": 0,
							},
						},
					},
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "foobar",
					Type: aws.AwsAmiResourceType,
					Attrs: &resource.Attributes{
						"architecture": "barfoo",
						"arn":          "foobar",
						"ebs_block_device": []map[string]interface{}{
							{
								"volume_type": "baz",
								"volume_size": 1,
							},
						},
					},
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id:   "foobar",
						Type: aws.AwsAmiResourceType,
						Attrs: &resource.Attributes{
							"architecture": "foobar",
							"arn":          "barfoo",
							"ebs_block_device": []map[string]interface{}{
								{
									"volume_type": "bar",
									"volume_size": 0,
								},
							},
						},
					},
				},
				summary: Summary{
					TotalResources: 1,
					TotalDrifted:   1,
					TotalManaged:   1,
				},
				differences: []Difference{
					{
						Res: &resource.Resource{
							Id:   "foobar",
							Type: aws.AwsAmiResourceType,
							Attrs: &resource.Attributes{
								"architecture": "foobar",
								"arn":          "barfoo",
								"ebs_block_device": []map[string]interface{}{
									{
										"volume_type": "bar",
										"volume_size": 0,
									},
								},
							},
						},
						Changelog: Changelog{
							{
								Change: diff.Change{
									Type: "update",
									From: "foobar",
									To:   "barfoo",
									Path: []string{
										"architecture",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "barfoo",
									To:   "foobar",
									Path: []string{
										"arn",
									},
								},
								Computed: true,
							},
							{
								Change: diff.Change{
									Type: "update",
									From: 0,
									To:   1,
									Path: []string{
										"ebs_block_device",
										"0",
										"volume_size",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "bar",
									To:   "baz",
									Path: []string{
										"ebs_block_device",
										"0",
										"volume_type",
									},
								},
							},
						},
					},
				},
				alerts: alerter.Alerts{
					"": {
						NewComputedDiffAlert(),
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "TestDiff with partial ignore",
			iac: []*resource.Resource{
				{
					Id:   "foobar",
					Type: aws.AwsAmiResourceType,
					Attrs: &resource.Attributes{
						"architecture": "foobar",
						"arn":          "barfoo",
					},
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "foobar",
					Type: aws.AwsAmiResourceType,
					Attrs: &resource.Attributes{
						"architecture": "barfoo",
						"arn":          "foobar",
					},
				},
			},
			ignoredDrift: []struct {
				res  *resource.Resource
				path []string
			}{
				{
					res: &resource.Resource{
						Id:   "foobar",
						Type: aws.AwsAmiResourceType,
						Attrs: &resource.Attributes{
							"architecture": "foobar",
							"arn":          "barfoo",
						},
					},
					path: []string{"architecture"},
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id:   "foobar",
						Type: aws.AwsAmiResourceType,
						Attrs: &resource.Attributes{
							"architecture": "foobar",
							"arn":          "barfoo",
						},
					},
				},
				summary: Summary{
					TotalResources: 1,
					TotalDrifted:   1,
					TotalManaged:   1,
				},
				differences: []Difference{
					{
						Res: &resource.Resource{
							Id:   "foobar",
							Type: aws.AwsAmiResourceType,
							Attrs: &resource.Attributes{
								"architecture": "foobar",
								"arn":          "barfoo",
							},
						},
						Changelog: Changelog{
							{
								Change: diff.Change{
									Type: "update",
									From: "barfoo",
									To:   "foobar",
									Path: []string{
										"arn",
									},
								},
								Computed: true,
							},
						},
					},
				},
				alerts: alerter.Alerts{
					"": {
						NewComputedDiffAlert(),
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "TestDiff with full ignore",
			iac: []*resource.Resource{
				{
					Id: "foobar",
					Attrs: &resource.Attributes{
						"foobar": "foobar",
						"barfoo": "barfoo",
					},
				},
			},
			ignoredRes: []*resource.Resource{
				{
					Id:    "should_be_ignored",
					Attrs: &resource.Attributes{},
				},
			},
			cloud: []*resource.Resource{
				{
					Id: "foobar",
					Attrs: &resource.Attributes{
						"foobar": "barfoo",
						"barfoo": "foobar",
					},
				},
				{
					Id:    "should_be_ignored",
					Attrs: &resource.Attributes{},
				},
			},
			ignoredDrift: []struct {
				res  *resource.Resource
				path []string
			}{
				{
					res: &resource.Resource{
						Id: "foobar",
						Attrs: &resource.Attributes{
							"foobar": "foobar",
							"barfoo": "barfoo",
						},
					},
					path: []string{"foobar"},
				},
				{
					res: &resource.Resource{
						Id: "foobar",
						Attrs: &resource.Attributes{
							"foobar": "foobar",
							"barfoo": "barfoo",
						},
					},
					path: []string{"barfoo"},
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id: "foobar",
						Attrs: &resource.Attributes{
							"foobar": "foobar",
							"barfoo": "barfoo",
						},
					},
				},
				summary: Summary{
					TotalResources: 1,
					TotalDrifted:   0,
					TotalManaged:   1,
				},
			},
			hasDrifted: false,
		},
		{
			name: "TestDiffWithAlertFiltering",
			iac: []*resource.Resource{
				{
					Id:   "foobar",
					Type: "fakeres",
					Attrs: &resource.Attributes{
						"foobar": "foobar",
						"barfoo": "barfoo",
						"struct": map[string]interface{}{
							"baz": "baz",
							"bar": "bar",
						},
					},
				},
				{
					Id:   "barfoo",
					Type: "fakeres",
					Attrs: &resource.Attributes{
						"foobar": "foobar",
						"barfoo": "barfoo",
						"struct": map[string]interface{}{
							"baz": "baz",
							"bar": "bar",
						},
					},
				},
				{
					Id:   "foobaz",
					Type: "other",
					Attrs: &resource.Attributes{
						"foobar": "foobar",
						"barfoo": "barfoo",
						"struct": map[string]interface{}{
							"baz": "baz",
							"bar": "bar",
						},
					},
				},
				{
					Id:   "resource",
					Type: "other",
					Attrs: &resource.Attributes{
						"foobar": "foobar",
						"barfoo": "barfoo",
						"struct": map[string]interface{}{
							"baz": "baz",
							"bar": "bar",
						},
						"structslice": []map[string]interface{}{
							{
								"string": "one",
								"array":  []string{"foo"},
							},
						},
					},
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "foobar",
					Type: "fakeres",
					Attrs: &resource.Attributes{
						"foobar": "barfoo",
						"barfoo": "foobar",
						"struct": map[string]interface{}{
							"baz": "bar",
							"bar": "baz",
						},
					},
				},
				{
					Id:   "barfoo",
					Type: "fakeres",
					Attrs: &resource.Attributes{
						"foobar": "barfoo",
						"barfoo": "foobar",
						"struct": map[string]interface{}{
							"baz": "bar",
							"bar": "baz",
						},
					},
				},
				{
					Id:   "foobaz",
					Type: "other",
					Attrs: &resource.Attributes{
						"foobar": "barfoo",
						"barfoo": "foobar",
						"struct": map[string]interface{}{
							"baz": "bar",
							"bar": "baz",
						},
					},
				},
				{
					Id:   "resource",
					Type: "other",
					Attrs: &resource.Attributes{
						"foobar": "barfoo",
						"barfoo": "foobar",
						"struct": map[string]interface{}{
							"baz": "bar",
							"bar": "baz",
						},
						"structslice": []map[string]interface{}{
							{
								"string": "two",
								"array":  []string{"oof"},
							},
						},
					},
				},
			},
			alerts: alerter.Alerts{
				"fakeres": {
					&alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
				},
				"other.foobaz": {
					&alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
				},
				"other.resource": {
					&alerter.FakeAlert{Msg: "Should not be ignored"},
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id:   "resource",
						Type: "other",
						Attrs: &resource.Attributes{
							"foobar": "foobar",
							"barfoo": "barfoo",
							"struct": map[string]interface{}{
								"baz": "baz",
								"bar": "bar",
							},
							"structslice": []map[string]interface{}{
								{
									"string": "one",
									"array":  []string{"foo"},
								},
							},
						},
					},
				},
				summary: Summary{
					TotalResources: 1,
					TotalDrifted:   1,
					TotalManaged:   1,
				},
				differences: []Difference{
					{
						Res: &resource.Resource{
							Id:   "resource",
							Type: "other",
							Attrs: &resource.Attributes{
								"foobar": "foobar",
								"barfoo": "barfoo",
								"struct": map[string]interface{}{
									"baz": "baz",
									"bar": "bar",
								},
								"structslice": []map[string]interface{}{
									{
										"string": "one",
										"array":  []string{"foo"},
									},
								},
							},
						},
						Changelog: Changelog{
							{
								Change: diff.Change{
									Type: "update",
									From: "barfoo",
									To:   "foobar",
									Path: []string{
										"barfoo",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "foobar",
									To:   "barfoo",
									Path: []string{
										"foobar",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "bar",
									To:   "baz",
									Path: []string{
										"struct",
										"bar",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "baz",
									To:   "bar",
									Path: []string{
										"struct",
										"baz",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "foo",
									To:   "oof",
									Path: []string{
										"structslice",
										"0",
										"array",
										"0",
									},
								},
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "one",
									To:   "two",
									Path: []string{
										"structslice",
										"0",
										"string",
									},
								},
							},
						},
					},
				},
				alerts: alerter.Alerts{
					"fakeres": {
						&alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
					},
					"other.foobaz": {
						&alerter.FakeAlert{Msg: "Should be ignored", IgnoreResource: true},
					},
					"other.resource": {
						&alerter.FakeAlert{Msg: "Should not be ignored"},
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "TestDiff with computed field send 1 alert",
			iac: []*resource.Resource{
				{
					Id:   "ID",
					Type: aws.AwsAmiResourceType,
					Attrs: &resource.Attributes{
						"id":  "ID",
						"arn": "ARN",
					},
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "ID",
					Type: aws.AwsAmiResourceType,
					Attrs: &resource.Attributes{
						"id":  "IDCHANGED",
						"arn": "ARNCHANGED",
					},
				},
			},
			alerts: alerter.Alerts{},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id:   "ID",
						Type: aws.AwsAmiResourceType,
						Attrs: &resource.Attributes{
							"id":  "ID",
							"arn": "ARN",
						},
					},
				},
				summary: Summary{
					TotalResources: 1,
					TotalDrifted:   1,
					TotalManaged:   1,
				},
				differences: []Difference{
					{
						Res: &resource.Resource{
							Id:   "ID",
							Type: aws.AwsAmiResourceType,
							Attrs: &resource.Attributes{
								"id":  "ID",
								"arn": "ARN",
							},
						},
						Changelog: Changelog{
							{
								Change: diff.Change{
									Type: "update",
									From: "ARN",
									To:   "ARNCHANGED",
									Path: []string{
										"arn",
									},
								},
								Computed: true,
							},
							{
								Change: diff.Change{
									Type: "update",
									From: "ID",
									To:   "IDCHANGED",
									Path: []string{
										"id",
									},
								},
								Computed: true,
							},
						},
					},
				},
				alerts: alerter.Alerts{
					"": {
						NewComputedDiffAlert(),
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "Test alert on unmanaged security group rules",
			iac: []*resource.Resource{
				{
					Id:   "managed security group",
					Type: aws.AwsSecurityGroupResourceType,
					Attrs: &resource.Attributes{
						"id": "managed security group",
					},
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "managed security group",
					Type: aws.AwsSecurityGroupResourceType,
					Attrs: &resource.Attributes{
						"id": "managed security group",
					},
				},
				{
					Id:   "unmanaged rule",
					Type: aws.AwsSecurityGroupRuleResourceType,
					Attrs: &resource.Attributes{
						"id": "unmanaged rule",
					},
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id:   "managed security group",
						Type: aws.AwsSecurityGroupResourceType,
						Attrs: &resource.Attributes{
							"id": "managed security group",
						},
					},
				},
				unmanaged: []*resource.Resource{
					{
						Id:   "unmanaged rule",
						Type: aws.AwsSecurityGroupRuleResourceType,
						Attrs: &resource.Attributes{
							"id": "unmanaged rule",
						},
					},
				},
				summary: Summary{
					TotalResources: 2,
					TotalManaged:   1,
					TotalUnmanaged: 1,
				},
				alerts: alerter.Alerts{
					"": {
						newUnmanagedSecurityGroupRulesAlert(),
					},
				},
			},
			hasDrifted: true,
		},
		{
			name: "Test sorted unmanaged & deleted resources",
			iac: []*resource.Resource{
				{
					Id:   "deleted resource 22",
					Type: "aws_s3_bucket",
				},
				{
					Id:   "deleted resource 20",
					Type: "aws_ebs_volume",
				},
				{
					Id:   "deleted resource 20",
					Type: "aws_s3_bucket",
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "unmanaged resource 12",
					Type: "aws_s3_bucket",
				},
				{
					Id:   "unmanaged resource 10",
					Type: "aws_s3_bucket",
				},
				{
					Id:   "unmanaged resource 11",
					Type: "aws_ebs_volume",
				},
			},
			expected: Analysis{
				managed: []*resource.Resource{},
				unmanaged: []*resource.Resource{
					{
						Id:   "unmanaged resource 11",
						Type: "aws_ebs_volume",
					},
					{
						Id:   "unmanaged resource 10",
						Type: "aws_s3_bucket",
					},
					{
						Id:   "unmanaged resource 12",
						Type: "aws_s3_bucket",
					},
				},
				deleted: []*resource.Resource{
					{
						Id:   "deleted resource 20",
						Type: "aws_ebs_volume",
					},
					{
						Id:   "deleted resource 20",
						Type: "aws_s3_bucket",
					},
					{
						Id:   "deleted resource 22",
						Type: "aws_s3_bucket",
					},
				},
				summary: Summary{
					TotalResources: 6,
					TotalManaged:   0,
					TotalUnmanaged: 3,
					TotalDeleted:   3,
				},
				alerts: alerter.Alerts{},
			},
			hasDrifted: true,
		},
		{
			name: "Test Discriminant function",
			iac: []*resource.Resource{
				{
					Id:   "foo",
					Type: aws.AwsAppAutoscalingTargetResourceType,
					Attrs: &resource.Attributes{
						"scalable_dimension": "test2",
					},
				},
			},
			cloud: []*resource.Resource{
				{
					Id:   "foo",
					Type: aws.AwsAppAutoscalingTargetResourceType,
					Attrs: &resource.Attributes{
						"scalable_dimension": "test1",
					},
				},
				{
					Id:   "foo",
					Type: aws.AwsAppAutoscalingTargetResourceType,
					Attrs: &resource.Attributes{
						"scalable_dimension": "test2",
					},
				},
			},
			hasDrifted: true,
			expected: Analysis{
				managed: []*resource.Resource{
					{
						Id:   "foo",
						Type: aws.AwsAppAutoscalingTargetResourceType,
						Attrs: &resource.Attributes{
							"scalable_dimension": "test2",
						},
					},
				},
				unmanaged: []*resource.Resource{
					{
						Id:   "foo",
						Type: aws.AwsAppAutoscalingTargetResourceType,
						Attrs: &resource.Attributes{
							"scalable_dimension": "test1",
						},
					},
				},
				summary: Summary{
					TotalResources: 2,
					TotalManaged:   1,
					TotalUnmanaged: 1,
				},
			},
		},
	}

	differ, err := diff.NewDiffer(diff.SliceOrdering(true))
	if err != nil {
		t.Fatalf("Error creating new differ: %e", err)
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			testFilter := &filter.MockFilter{}
			for _, ignored := range c.ignoredRes {
				testFilter.On("IsResourceIgnored", ignored).Return(true)
			}
			testFilter.On("IsResourceIgnored", mock.Anything).Return(false)

			for _, s := range c.ignoredDrift {
				testFilter.On("IsFieldIgnored", s.res, s.path).Return(true)
			}
			testFilter.On("IsFieldIgnored", mock.Anything, mock.Anything).Return(false)

			al := alerter.NewAlerter()
			if c.alerts != nil {
				al.SetAlerts(c.alerts)
			}

			repo := testresource.InitFakeSchemaRepository("aws", "3.19.0")
			aws.InitResourcesMetadata(repo)

			analyzer := NewAnalyzer(al, AnalyzerOptions{Deep: true}, testFilter)

			for _, res := range c.cloud {
				addSchemaToRes(res, repo)
			}

			for _, res := range c.iac {
				addSchemaToRes(res, repo)
			}

			for _, res := range c.ignoredRes {
				addSchemaToRes(res, repo)
			}

			for _, drift := range c.ignoredDrift {
				addSchemaToRes(drift.res, repo)
			}

			analysis := NewAnalysis(AnalyzerOptions{Deep: true})
			analysis = analyzer.CompareEnumeration(analysis, c.cloud, c.iac)
			analysis = analyzer.CompleteAnalysis(analysis, c.cloud, c.iac)
			analysis.SortResources()

			if err != nil {
				t.Error(err)
				return
			}

			if analysis.IsSync() == c.hasDrifted {
				t.Errorf("Drifted state does not match, got %t expected %t", analysis.IsSync(), !c.hasDrifted)
			}

			managedChanges, err := differ.Diff(analysis.Managed(), c.expected.Managed())
			if err != nil {
				t.Fatalf("Unable to compare %+v", err)
			}
			if len(managedChanges) > 0 {
				for _, change := range managedChanges {
					t.Errorf("%+v", change)
				}
			}

			unmanagedChanges, err := differ.Diff(analysis.Unmanaged(), c.expected.Unmanaged())
			if err != nil {
				t.Fatalf("Unable to compare %+v", err)
			}
			if len(unmanagedChanges) > 0 {
				for _, change := range unmanagedChanges {
					t.Errorf("%+v", change)
				}
			}

			deletedChanges, err := differ.Diff(analysis.Deleted(), c.expected.Deleted())
			if err != nil {
				t.Fatalf("Unable to compare %+v", err)
			}
			if len(deletedChanges) > 0 {
				for _, change := range deletedChanges {
					t.Errorf("%+v", change)
				}
			}

			diffChanges, err := differ.Diff(analysis.Differences(), c.expected.Differences())
			if err != nil {
				t.Fatalf("Unable to compare %+v", err)
			}
			if len(diffChanges) > 0 {
				for _, change := range diffChanges {
					t.Errorf("%+v", change)
				}
			}

			summaryChanges, err := differ.Diff(c.expected.Summary(), analysis.Summary())
			if err != nil {
				t.Fatalf("Unable to compare %+v", err)
			}
			if len(summaryChanges) > 0 {
				for _, change := range summaryChanges {
					t.Errorf("%+v", change)
				}
			}

			alertsChanges, err := differ.Diff(analysis.Alerts(), c.expected.Alerts())
			if err != nil {
				t.Fatalf("Unable to compare %+v", err)
			}
			if len(alertsChanges) > 0 {
				for _, change := range alertsChanges {
					t.Errorf("%+v", change)
				}
			}
		})
	}
}

func addSchemaToRes(res *resource.Resource, repo resource.SchemaRepositoryInterface) {
	schema, _ := repo.GetSchema(res.ResourceType())
	res.Sch = schema
}

func TestAnalysis_MarshalJSON(t *testing.T) {
	goldenFile := "./testdata/output.json"
	analysis := Analysis{}
	analysis.AddManaged(
		&resource.Resource{
			Id:   "AKIA5QYBVVD25KFXJHYJ",
			Type: "aws_iam_access_key",
		}, &resource.Resource{
			Id:   "driftctl2",
			Type: "aws_managed_resource",
		},
	)
	analysis.AddUnmanaged(
		&resource.Resource{
			Id:   "driftctl",
			Type: "aws_s3_bucket_policy",
		}, &resource.Resource{
			Id:   "driftctl",
			Type: "aws_s3_bucket_notification",
		},
	)
	analysis.AddDeleted(
		&resource.Resource{
			Id:   "test-driftctl2",
			Type: "aws_iam_user",
			Attrs: &resource.Attributes{
				"foobar": "test",
			},
		},
		&resource.Resource{
			Id:   "AKIA5QYBVVD2Y6PBAAPY",
			Type: "aws_iam_access_key",
		},
	)
	analysis.AddDifference(Difference{
		Res: &resource.Resource{
			Id:   "AKIA5QYBVVD25KFXJHYJ",
			Type: "aws_iam_access_key",
			Source: &resource.TerraformStateSource{
				State:  "tfstate://terraform.tfstate",
				Module: "module",
				Name:   "my_name",
			},
		},
		Changelog: []Change{
			{
				Change: diff.Change{
					Type: "update",
					Path: []string{"status"},
					From: "Active",
					To:   "Inactive",
				},
			},
		},
	})
	analysis.SetAlerts(alerter.Alerts{
		"aws_iam_access_key": {
			&alerter.FakeAlert{Msg: "This is an alert"},
		},
	})
	analysis.ProviderName = "AWS"
	analysis.ProviderVersion = "2.18.5"

	got, err := json.MarshalIndent(analysis, "", "\t")
	if err != nil {
		t.Fatal(err)
	}
	if *goldenfile.Update == "TestAnalysis_MarshalJSON" {
		if err := ioutil.WriteFile(goldenFile, got, 0600); err != nil {
			t.Fatal(err)
		}
	}
	expected, err := ioutil.ReadFile(goldenFile)
	if err != nil {
		t.Fatal(err)
	}
	assert.Nil(t, err)
	assert.Equal(t, string(expected), string(got))
}

func TestAnalysis_UnmarshalJSON(t *testing.T) {
	expected := Analysis{
		summary: Summary{
			TotalResources: 6,
			TotalDrifted:   1,
			TotalUnmanaged: 2,
			TotalDeleted:   2,
			TotalManaged:   2,
		},
		managed: []*resource.Resource{
			{
				Id:   "AKIA5QYBVVD25KFXJHYJ",
				Type: "aws_iam_access_key",
			},
			{
				Id:   "test-managed",
				Type: "aws_iam_user",
			},
		},
		unmanaged: []*resource.Resource{
			{
				Id:   "driftctl",
				Type: "aws_s3_bucket_policy",
			},
			{
				Id:   "driftctl",
				Type: "aws_s3_bucket_notification",
			},
		},
		deleted: []*resource.Resource{
			{
				Id:   "test-driftctl2",
				Type: "aws_iam_user",
			},
			{
				Id:   "AKIA5QYBVVD2Y6PBAAPY",
				Type: "aws_iam_access_key",
			},
		},
		differences: []Difference{
			{
				Res: &resource.Resource{
					Id:   "AKIA5QYBVVD25KFXJHYJ",
					Type: "aws_iam_access_key",
				},
				Changelog: []Change{
					{
						Change: diff.Change{
							Type: "update",
							Path: []string{"status"},
							From: "Active",
							To:   "Inactive",
						},
					},
				},
			},
		},
		alerts: alerter.Alerts{
			"aws_iam_access_key": {
				&alerter.SerializedAlert{
					Msg: "This is an alert",
				},
			},
		},
		ProviderName:    "AWS",
		ProviderVersion: "2.18.5",
	}

	got := Analysis{}
	input, err := ioutil.ReadFile("./testdata/input.json")
	if err != nil {
		t.Fatal(err)
	}
	err = json.Unmarshal(input, &got)
	if err != nil {
		t.Fatal(err)
	}
	assert.Equal(t, expected, got)
	assert.Equal(t, 33, got.Coverage())
	assert.Equal(t, 2, got.Summary().TotalUnmanaged)
	assert.Equal(t, 2, got.Summary().TotalManaged)
	assert.Equal(t, 2, got.Summary().TotalDeleted)
	assert.Equal(t, 6, got.Summary().TotalResources)
	assert.Equal(t, 1, got.Summary().TotalDrifted)
	assert.Len(t, got.alerts, 1)
	assert.Equal(t, got.alerts["aws_iam_access_key"][0].Message(), "This is an alert")
}
