// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu

import (
	"fmt"
	"testing"

	"github.com/hashicorp/hcl/v2"
	"github.com/opentofu/opentofu/internal/lang/marks"
	"github.com/opentofu/opentofu/internal/tfdiags"
)

func TestParseDeprecatedWarningLevel(t *testing.T) {
	tests := []struct {
		in   string
		want DeprecationWarningLevel
	}{
		{want: DeprecationWarningLevelAll, in: ""},
		{want: DeprecationWarningLevelAll, in: "all"},
		{want: DeprecationWarningLevelLocal, in: "local"},
		{want: DeprecationWarningLevelNone, in: "none"},
		{want: DeprecationWarningLevelAll, in: "off"},
		{want: DeprecationWarningLevelAll, in: "remote"},
		{want: DeprecationWarningLevelAll, in: "wrongLevel"},
	}
	for _, tt := range tests {
		t.Run(fmt.Sprintf("%q level parsing", tt.in), func(t *testing.T) {
			if got, want := ParseDeprecatedWarningLevel(tt.in), tt.want; got != want {
				t.Errorf("parsing %s deprecation level resulted in a wrong value. got: %s; want: %s", tt.in, got, want)
			}
		})
	}
}

func TestDeprecationDiagnosticAllowed(t *testing.T) {
	tests := map[string]struct {
		diag tfdiags.Diagnostics
		lvl  DeprecationWarningLevel
		want bool
	}{
		// ensure that diagnostics unrelated with the deprecation ones are not filtered out
		"diagnostic without extra info": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil)),
			lvl:  DeprecationWarningLevelNone,
			want: true,
		},
		"diagnostic with extra info but unrelated to deprecation": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: struct{ foo string }{foo: "bar"},
			}),
			lvl:  DeprecationWarningLevelNone,
			want: true,
		},
		// test the type of diagnostics that are generated by the deprecated outputs checks
		"remote deprecated output attribute with all level": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.Override(
				tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil),
				tfdiags.Warning,
				marks.DeprecatedOutputDiagnosticOverride(marks.DeprecationCause{IsFromRemoteModule: true}),
			)),
			lvl:  DeprecationWarningLevelAll,
			want: true,
		},
		"remote deprecated output attribute with local level": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.Override(
				tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil),
				tfdiags.Warning,
				marks.DeprecatedOutputDiagnosticOverride(marks.DeprecationCause{IsFromRemoteModule: true}),
			)),
			lvl:  DeprecationWarningLevelLocal,
			want: false,
		},
		"remote deprecated output attribute with none level": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.Override(
				tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil),
				tfdiags.Warning,
				marks.DeprecatedOutputDiagnosticOverride(marks.DeprecationCause{IsFromRemoteModule: true}),
			)),
			lvl:  DeprecationWarningLevelNone,
			want: false,
		},
		"local deprecated output attribute with all level": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.Override(
				tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil),
				tfdiags.Warning,
				marks.DeprecatedOutputDiagnosticOverride(marks.DeprecationCause{IsFromRemoteModule: false}),
			)),
			lvl:  DeprecationWarningLevelAll,
			want: true,
		},
		"local deprecated output attribute with local level": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.Override(
				tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil),
				tfdiags.Warning,
				marks.DeprecatedOutputDiagnosticOverride(marks.DeprecationCause{IsFromRemoteModule: false}),
			)),
			lvl:  DeprecationWarningLevelLocal,
			want: true,
		},
		"local deprecated output attribute with none level": {
			diag: tfdiags.Diagnostics{}.Append(tfdiags.Override(
				tfdiags.AttributeValue(tfdiags.Warning, "summary", "details", nil),
				tfdiags.Warning,
				marks.DeprecatedOutputDiagnosticOverride(marks.DeprecationCause{IsFromRemoteModule: false}),
			)),
			lvl:  DeprecationWarningLevelNone,
			want: false,
		},
		"remote deprecated output expr with all level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: marks.DeprecationCause{IsFromRemoteModule: true},
			}),
			lvl:  DeprecationWarningLevelAll,
			want: true,
		},
		"remote deprecated output expr with local level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: marks.DeprecationCause{IsFromRemoteModule: true},
			}),
			lvl:  DeprecationWarningLevelLocal,
			want: false,
		},
		"remote deprecated output expr with none level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: marks.DeprecationCause{IsFromRemoteModule: true},
			}),
			lvl:  DeprecationWarningLevelNone,
			want: false,
		},
		"local deprecated output expr with all level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: marks.DeprecationCause{IsFromRemoteModule: false},
			}),
			lvl:  DeprecationWarningLevelAll,
			want: true,
		},
		"local deprecated output expr with local level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: marks.DeprecationCause{IsFromRemoteModule: false},
			}),
			lvl:  DeprecationWarningLevelLocal,
			want: true,
		},
		"local deprecated output expr with none level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: marks.DeprecationCause{IsFromRemoteModule: false},
			}),
			lvl:  DeprecationWarningLevelNone,
			want: false,
		},
		// test the type of diagnostics that are generated by the deprecated variables checks
		"remote variable with all level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: VariableDeprecationCause{IsFromRemoteModule: true},
			}),
			lvl:  DeprecationWarningLevelAll,
			want: true,
		},
		"remote variable with local level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: VariableDeprecationCause{IsFromRemoteModule: true},
			}),
			lvl:  DeprecationWarningLevelLocal,
			want: false,
		},
		"remote variable with none level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: VariableDeprecationCause{IsFromRemoteModule: true},
			}),
			lvl:  DeprecationWarningLevelNone,
			want: false,
		},
		"local variable with all level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: VariableDeprecationCause{IsFromRemoteModule: false},
			}),
			lvl:  DeprecationWarningLevelAll,
			want: true,
		},
		"local variable with local level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: VariableDeprecationCause{IsFromRemoteModule: false},
			}),
			lvl:  DeprecationWarningLevelLocal,
			want: true,
		},
		"local variable with none level": {
			diag: tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
				Extra: VariableDeprecationCause{IsFromRemoteModule: false},
			}),
			lvl:  DeprecationWarningLevelNone,
			want: false,
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			seen := DeprecationDiagnosticAllowedSeen{}
			if got, want := DeprecationDiagnosticAllowed(tt.lvl, tt.diag[0], seen), tt.want; got != want {
				if want {
					t.Fatalf("expected diagnostic to be allowed but it is not")
				} else {
					t.Fatalf("expected diagnostic to not be allowed but it is")
				}
			}

			if tt.lvl != DeprecationWarningLevelNone {
				if got := DeprecationDiagnosticAllowed(tt.lvl, tt.diag[0], seen); got == true {
					t.Fatalf("expected duplicate diagnostic to be ignored")
				}
			}
		})
	}
}
