// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//  1. Redistributions of source code must retain the above copyright notice, this
//     list of conditions and the following disclaimer.
//
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//  3. Neither the name of the copyright holder nor the names of its
//     contributors may be used to endorse or promote products derived from
//     this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package expectations

import (
	"context"
	"path/filepath"
	"testing"

	"dawn.googlesource.com/dawn/tools/src/cmd/cts/common"
	"dawn.googlesource.com/dawn/tools/src/fileutils"
	"dawn.googlesource.com/dawn/tools/src/oswrapper"
	"github.com/stretchr/testify/require"
)

func TestUpdateExpectations(t *testing.T) {
	tests := []struct {
		name                 string
		resultsFile          string
		resultsContent       string
		testListPath         string
		testListContent      string
		expectationsPath     string
		expectationsContent  string
		expectationsFlag     []string
		wantErr              string
		wantLog              string
		verifyContent        func(t *testing.T, fs oswrapper.FilesystemReader, path string)
		generateExplicitTags bool
	}{
		{
			name:             "Success",
			resultsFile:      "results.txt",
			resultsContent:   "webgpu:test:one linux,nvidia Failure 0s false\ncore",
			testListPath:     "third_party/gn/webgpu-cts/test_list.txt",
			testListContent:  "webgpu:test:one\nwebgpu:test:two",
			expectationsPath: "expectations.txt",
			expectationsContent: `# BEGIN TAG HEADER
# OS
# tags: [ mac win linux android ]
# GPU
# tags: [ amd intel nvidia qualcomm ]
# Device
# tags: [ android-pixel-4 android-pixel-6 chromeos-board-amd64-generic fuchsia-board-qemu-x64 ]
# END TAG HEADER

# existing
[ mac ] webgpu:test:two: [ Failure ]
`,
			expectationsFlag: []string{"expectations.txt"},
			verifyContent: func(t *testing.T, fs oswrapper.FilesystemReader, path string) {
				content, err := fs.ReadFile(path)
				require.NoError(t, err)
				text := string(content)
				require.Contains(t, text, "[ linux nvidia ] webgpu:test:one: [ Failure ]")
				require.Contains(t, text, "[ mac ] webgpu:test:two: [ Failure ]")
			},
		},
		{
			name:             "Results Fetch Error",
			resultsFile:      "nonexistent.txt",
			testListPath:     "third_party/gn/webgpu-cts/test_list.txt",
			testListContent:  "webgpu:test:one",
			expectationsPath: "expectations.txt",
			expectationsFlag: []string{"expectations.txt"},
			wantErr:          "does not exist",
		},
		{
			name:             "Test List Load Error",
			resultsFile:      "results.txt",
			resultsContent:   "webgpu:test:one linux Failure 0s false\ncore",
			testListPath:     "nonexistent/test_list.txt",
			expectationsPath: "expectations.txt",
			expectationsFlag: []string{"expectations.txt"},
			wantErr:          "failed to load test list",
		},
		{
			name:             "Expectations Load Error",
			resultsFile:      "results.txt",
			resultsContent:   "webgpu:test:one linux Failure 0s false\ncore",
			testListPath:     "third_party/gn/webgpu-cts/test_list.txt",
			testListContent:  "webgpu:test:one\nwebgpu:test:two",
			expectationsPath: "expectations.txt",
			expectationsFlag: []string{"nonexistent.txt"},
			wantErr:          "does not exist",
		},
		{
			name:                "Expectations Validation Error",
			resultsFile:         "results.txt",
			resultsContent:      "webgpu:test:one linux Failure 0s false\ncore",
			testListPath:        "third_party/gn/webgpu-cts/test_list.txt",
			testListContent:     "webgpu:test:one",
			expectationsPath:    "expectations.txt",
			expectationsContent: "INVALID SYNTAX",
			expectationsFlag:    []string{"expectations.txt"},
			wantErr:             "expected status",
		},
		{
			name:             "Compat Mode",
			resultsFile:      "results.txt",
			resultsContent:   "webgpu:test:compat linux,nvidia Failure 0s false\ncompat",
			testListPath:     "third_party/gn/webgpu-cts/test_list.txt",
			testListContent:  "webgpu:test:compat",
			expectationsPath: "compat_expectations.txt",
			expectationsContent: `# BEGIN TAG HEADER
# OS
# tags: [ mac win linux android ]
# GPU
# tags: [ amd intel nvidia qualcomm ]
# Device
# tags: [ android-pixel-4 android-pixel-6 chromeos-board-amd64-generic fuchsia-board-qemu-x64 ]
# END TAG HEADER
`,
			expectationsFlag: []string{"compat_expectations.txt"},
			verifyContent: func(t *testing.T, fs oswrapper.FilesystemReader, path string) {
				content, err := fs.ReadFile(path)
				require.NoError(t, err)
				require.Contains(t, string(content), "[ linux nvidia ] webgpu:test:compat: [ Failure ]")
			},
		},
		{
			name:             "Unknown Tests Removal",
			resultsFile:      "results.txt",
			resultsContent:   "webgpu:test:known linux,nvidia Failure 0s false\ncore",
			testListPath:     "third_party/gn/webgpu-cts/test_list.txt",
			testListContent:  "webgpu:test:known",
			expectationsPath: "expectations.txt",
			expectationsContent: `# BEGIN TAG HEADER
# OS
# tags: [ mac win linux android ]
# GPU
# tags: [ amd intel nvidia qualcomm ]
# Device
# tags: [ android-pixel-4 android-pixel-6 chromeos-board-amd64-generic fuchsia-board-qemu-x64 ]
# END TAG HEADER

webgpu:test:unknown: [ Failure ]`,
			expectationsFlag: []string{"expectations.txt"},
			verifyContent: func(t *testing.T, fs oswrapper.FilesystemReader, path string) {
				content, err := fs.ReadFile(path)
				require.NoError(t, err)
				require.NotContains(t, string(content), "webgpu:test:unknown")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			wrapper := oswrapper.CreateFSTestOSWrapper()
			realWrapper := oswrapper.GetRealOSWrapper()
			rootDir := fileutils.DawnRoot(realWrapper)

			// Helper to write file relative to root
			write := func(path, content string) {
				fullPath := filepath.Join(rootDir, path)
				require.NoError(t, wrapper.MkdirAll(filepath.Dir(fullPath), 0755))
				require.NoError(t, wrapper.WriteFile(fullPath, []byte(content), 0666))
			}

			if tt.resultsContent != "" {
				write(tt.resultsFile, tt.resultsContent)
			}
			if tt.testListContent != "" {
				write(tt.testListPath, tt.testListContent)
			}
			if tt.expectationsContent != "" {
				write(tt.expectationsPath, tt.expectationsContent)
			}

			c := &cmd{}

			// Resolve paths to absolute for flags
			absExpectations := make([]string, len(tt.expectationsFlag))
			for i, p := range tt.expectationsFlag {
				absExpectations[i] = filepath.Join(rootDir, p)
			}
			c.flags.expectations = absExpectations

			if tt.resultsFile != "" {
				c.flags.results.File = filepath.Join(rootDir, tt.resultsFile)
			}

			c.flags.verbose = true
			c.flags.generateExplicitTags = tt.generateExplicitTags

			ctx := context.Background()
			cfg := common.Config{OsWrapper: wrapper}

			write("DEPS", "") // Ensure DawnRoot detection works

			err := c.Run(ctx, cfg)
			if tt.wantErr != "" {
				require.Error(t, err)
				require.ErrorContains(t, err, tt.wantErr)
			} else {
				require.NoError(t, err)
			}

			if tt.verifyContent != nil {
				// Check content of the first expectations file
				path := filepath.Join(rootDir, tt.expectationsPath)
				tt.verifyContent(t, wrapper, path)
			}
		})
	}
}
