package signer

import (
	"bytes"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	testCertFile = "testdata/ci.crt"
	testKeyFile  = "testdata/ci-private-key.pem"
)

type mockSignableTemplate struct {
	imports []string
	hasCode bool
}

func (m *mockSignableTemplate) GetFileImports() []string {
	return m.imports
}

func (m *mockSignableTemplate) HasCodeProtocol() bool {
	return m.hasCode
}

var signer, _ = NewTemplateSignerFromFiles(testCertFile, testKeyFile)

func TestTemplateSignerSignAndVerify(t *testing.T) {
	tempDir := t.TempDir()

	tests := []struct {
		name            string
		data            []byte
		tmpl            SignableTemplate
		wantSignErr     bool
		wantVerifyErr   bool
		wantVerified    bool
		modifyAfterSign func([]byte) []byte
	}{
		{
			name:         "Simple template",
			data:         []byte("id: test-template\ninfo:\n  name: Test Template"),
			tmpl:         &mockSignableTemplate{},
			wantVerified: true,
		},
		{
			name:         "Template with CRLF line endings",
			data:         []byte("id: test-template\r\ninfo:\r\n  name: Test Template"),
			tmpl:         &mockSignableTemplate{},
			wantVerified: true,
		},
		{
			name: "Template with imports",
			data: []byte("id: test-template\ninfo:\n  name: Test Template"),
			tmpl: &mockSignableTemplate{imports: []string{
				filepath.Join(tempDir, "import1.yaml"),
				filepath.Join(tempDir, "import2.yaml"),
			}},
			wantVerified: true,
		},
		{
			name:         "Template with code protocol",
			data:         []byte("id: test-template\ninfo:\n  name: Test Template\n\ncode:\n  - engine: bash\n    source: echo 'Hello, World!'"),
			tmpl:         &mockSignableTemplate{hasCode: true},
			wantSignErr:  false,
			wantVerified: true,
		},
		{
			name: "Tampered template",
			data: []byte("id: test-template\ninfo:\n  name: Test Template"),
			tmpl: &mockSignableTemplate{},
			modifyAfterSign: func(data []byte) []byte {
				signatureIndex := bytes.LastIndex(data, []byte(SignaturePattern))
				if signatureIndex == -1 {
					return data
				}
				return append(data[:signatureIndex], append([]byte("# Tampered content\n"), data[signatureIndex:]...)...)
			},
			wantVerified: false,
		},
		{
			name: "Invalid signature",
			data: []byte("id: test-template\ninfo:\n  name: Test Template"),
			tmpl: &mockSignableTemplate{},
			modifyAfterSign: func(data []byte) []byte {
				return append(bytes.TrimSuffix(data, []byte("\n")), []byte("\n# digest: invalid_signature:fragment")...)
			},
			wantVerifyErr: true,
			wantVerified:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create import files if needed
			for _, imp := range tt.tmpl.GetFileImports() {
				err := os.WriteFile(imp, []byte("imported content"), 0644)
				require.NoError(t, err, "Failed to create import file")
			}

			// Sign the template
			signature, err := signer.Sign(tt.data, tt.tmpl)
			if tt.wantSignErr {
				assert.Error(t, err, "Expected an error during signing")
				return
			}
			require.NoError(t, err, "Failed to sign template")

			// Append signature to the template data
			signedData := append(tt.data, []byte("\n"+signature)...)

			// Apply any modifications after signing if specified
			if tt.modifyAfterSign != nil {
				signedData = tt.modifyAfterSign(signedData)
			}

			// Verify the signature
			verified, err := signer.Verify(signedData, tt.tmpl)
			if tt.wantVerifyErr {
				assert.Error(t, err, "Expected an error during verification")
			} else {
				assert.NoError(t, err, "Unexpected error during verification")
			}
			assert.Equal(t, tt.wantVerified, verified, "Unexpected verification result")
		})
	}
}
