//
// Copyright 2024 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package schemavalidators_test

import (
	"encoding/json"
	"os"
	"testing"

	"github.com/chainloop-dev/chainloop/internal/schemavalidators"
	"github.com/stretchr/testify/require"
)

func TestValidateCycloneDX1_5(t *testing.T) {
	testCases := []struct {
		name     string
		filePath string
		wantErr  string
	}{
		{
			name:     "invalid sbom format",
			filePath: "./testdata/sbom-spdx.json",
			wantErr:  "missing properties: 'bomFormat', 'specVersion'",
		},
		{
			name:     "invalid clycondx format",
			filePath: "./testdata/openvex_v0.2.0.json",
			wantErr:  "missing properties: 'bomFormat', 'specVersion'",
		},
		{
			name:     "1.4 version",
			filePath: "./testdata/sbom.cyclonedx.json",
		},
		{
			name:     "1.5 version",
			filePath: "./testdata/sbom.cyclonedx-1.5.json",
		},
		{
			name:     "1.6 version error when parsing 1.6 specific fields",
			filePath: "./testdata/sbom.cyclonedx-1.6.json",
			wantErr:  "value must be one of \"application\", \"framework\", \"library\",",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			f, err := os.ReadFile(tc.filePath)
			require.NoError(t, err)

			var v interface{}
			require.NoError(t, json.Unmarshal(f, &v))

			err = schemavalidators.ValidateCycloneDX(v, "1.5")
			if tc.wantErr != "" {
				require.ErrorContains(t, err, tc.wantErr)
				return
			}

			require.NoError(t, err)
		})
	}
}

func TestValidateCycloneDX1_6(t *testing.T) {
	testCases := []struct {
		name     string
		filePath string
		wantErr  string
	}{
		{
			name:     "invalid sbom format",
			filePath: "./testdata/sbom-spdx.json",
			wantErr:  " missing properties: 'bomFormat', 'specVersion'",
		},
		{
			name:     "invalid clycondx format",
			filePath: "./testdata/openvex_v0.2.0.json",
			wantErr:  "missing properties: 'bomFormat', 'specVersion'",
		},
		{
			name:     "1.4 version",
			filePath: "./testdata/sbom.cyclonedx.json",
		},
		{
			name:     "1.5 version",
			filePath: "./testdata/sbom.cyclonedx-1.5.json",
		},
		{
			name:     "1.6 version",
			filePath: "./testdata/sbom.cyclonedx-1.6.json",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			f, err := os.ReadFile(tc.filePath)
			require.NoError(t, err)

			var v interface{}
			require.NoError(t, json.Unmarshal(f, &v))

			err = schemavalidators.ValidateCycloneDX(v, "1.6")
			if tc.wantErr != "" {
				require.ErrorContains(t, err, tc.wantErr)
				return
			}

			require.NoError(t, err)
		})
	}
}

func TestValidateCSAF_2_0(t *testing.T) {
	testCases := []struct {
		name     string
		filePath string
		wantErr  string
	}{
		{
			name:     "invalid sbom format",
			filePath: "./testdata/sbom-spdx.json",
			wantErr:  "missing properties: 'document'",
		},
		{
			name:     "invalid csaf format",
			filePath: "./testdata/openvex_v0.2.0.json",
			wantErr:  "missing properties: 'document'",
		},
		{
			name:     "2.0 vex",
			filePath: "./testdata/csaf_vex_v0.2.0.json",
		},
		{
			name:     "2.0 security advisory",
			filePath: "./testdata/csaf_security_advisory.json",
		},
		{
			name:     "2.0 informational advisory",
			filePath: "./testdata/csaf_informational_advisory.json",
		},
		{
			name:     "2.0 security incident response",
			filePath: "./testdata/csaf_security_incident_response.json",
		},
		{
			name:     "2.1 vex",
			filePath: "./testdata/csaf_vex_v0.2.1.json",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			f, err := os.ReadFile(tc.filePath)
			require.NoError(t, err)

			var v interface{}
			require.NoError(t, json.Unmarshal(f, &v))

			err = schemavalidators.ValidateCSAF(v)
			if tc.wantErr != "" {
				require.ErrorContains(t, err, tc.wantErr)
				return
			}

			require.NoError(t, err)
		})
	}
}

func TestValidateRunnerContext(t *testing.T) {
	testCases := []struct {
		name     string
		filePath string
		wantErr  string
	}{
		{
			name:     "0.1 GitHub branch protection",
			filePath: "./testdata/runner_context_branches-v0.1.json",
		},
		{
			name:     "0.1 GitHub rulesets",
			filePath: "./testdata/runner_context_rulesets-v0.1.json",
		},
		{
			name:     "0.1 GitLab no protection",
			filePath: "./testdata/runner_context_gitlab_no_protection-v0.1.json",
		},
		{
			name:     "invalid Chainloop runner context - missing meta",
			filePath: "./testdata/runner_context_missing-meta-v0.1.json",
			wantErr:  "missing properties: 'meta'",
		},
		{
			name:     "invalid Chainloop runner context - missing data",
			filePath: "./testdata/runner_context_missing-data-v0.1.json",
			wantErr:  "missing properties: 'data'",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			f, err := os.ReadFile(tc.filePath)
			require.NoError(t, err)

			var v interface{}
			require.NoError(t, json.Unmarshal(f, &v))

			err = schemavalidators.ValidateChainloopRunnerContext(v, "")
			if tc.wantErr != "" {
				require.ErrorContains(t, err, tc.wantErr)
				return
			}

			require.NoError(t, err)
		})
	}
}
