// ABOUTME: Comprehensive tests for EdgeConnect configuration parser with validation scenarios
// ABOUTME: Tests all validation rules, error conditions, and successful parsing cases
package config

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

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

func TestNewParser(t *testing.T) {
	parser := NewParser()
	assert.NotNil(t, parser)
	assert.IsType(t, &ConfigParser{}, parser)
}

func TestConfigParser_ParseBytes(t *testing.T) {
	parser := NewParser()

	tests := []struct {
		name    string
		yaml    string
		wantErr bool
		errMsg  string
	}{
		{
			name: "valid k8s config",
			yaml: `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  k8sApp:
    manifestFile: "./test-manifest.yaml"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`,
			wantErr: true, // Will fail because manifest file doesn't exist
			errMsg:  "manifestFile does not exist",
		},
		{
			name: "valid docker config",
			yaml: `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  dockerApp:
    image: "nginx:latest"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`,
			wantErr: false,
		},
		{
			name: "missing kind",
			yaml: `
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  k8sApp:
    manifestFile: "./test-manifest.yaml"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`,
			wantErr: true,
			errMsg:  "kind is required",
		},
		{
			name: "invalid kind",
			yaml: `
kind: invalid-kind
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  dockerApp:
    image: "nginx:latest"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`,
			wantErr: true,
			errMsg:  "unsupported kind: invalid-kind",
		},
		{
			name: "missing app definition",
			yaml: `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`,
			wantErr: true,
			errMsg:  "spec must define either k8sApp or dockerApp",
		},
		{
			name: "both k8s and docker apps",
			yaml: `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  k8sApp:
    manifestFile: "./test-manifest.yaml"
  dockerApp:
    appName: "test-app"
    image: "nginx:latest"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`,
			wantErr: true,
			errMsg:  "spec cannot define both k8sApp and dockerApp",
		},
		{
			name: "empty infrastructure template",
			yaml: `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  dockerApp:
    image: "nginx:latest"
  infraTemplate: []
`,
			wantErr: true,
			errMsg:  "infraTemplate is required and must contain at least one target",
		},
		{
			name: "with network config",
			yaml: `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  dockerApp:
    image: "nginx:latest"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
  network:
    outboundConnections:
      - protocol: "tcp"
        portRangeMin: 80
        portRangeMax: 80
        remoteCIDR: "0.0.0.0/0"
`,
			wantErr: false,
		},
		{
			name:    "empty data",
			yaml:    "",
			wantErr: true,
			errMsg:  "configuration data cannot be empty",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config, err := parser.ParseBytes([]byte(tt.yaml))

			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				assert.Nil(t, config)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, config)
			}
		})
	}
}

func TestConfigParser_ParseFile(t *testing.T) {
	parser := NewParser()

	// Create temporary directory for test files
	tempDir := t.TempDir()

	// Create a valid config file
	validConfig := `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  dockerApp:
    image: "nginx:latest"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`

	validFile := filepath.Join(tempDir, "valid.yaml")
	err := os.WriteFile(validFile, []byte(validConfig), 0644)
	require.NoError(t, err)

	// Test valid file parsing
	config, _, err := parser.ParseFile(validFile)
	assert.NoError(t, err)
	assert.NotNil(t, config)
	assert.Equal(t, "edgeconnect-deployment", config.Kind)
	assert.Equal(t, "test-app", config.Metadata.Name)

	// Test non-existent file
	nonExistentFile := filepath.Join(tempDir, "nonexistent.yaml")
	config, _, err = parser.ParseFile(nonExistentFile)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "does not exist")
	assert.Nil(t, config)

	// Test empty filename
	config, _, err = parser.ParseFile("")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "filename cannot be empty")
	assert.Nil(t, config)

	// Test invalid YAML
	invalidFile := filepath.Join(tempDir, "invalid.yaml")
	err = os.WriteFile(invalidFile, []byte("invalid: yaml: content: ["), 0644)
	require.NoError(t, err)

	config, _, err = parser.ParseFile(invalidFile)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "YAML parsing failed")
	assert.Nil(t, config)
}

func TestConfigParser_RelativePathResolution(t *testing.T) {
	parser := NewParser()
	tempDir := t.TempDir()

	// Create a manifest file
	manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n  name: test\n"
	manifestFile := filepath.Join(tempDir, "manifest.yaml")
	err := os.WriteFile(manifestFile, []byte(manifestContent), 0644)
	require.NoError(t, err)

	// Create config with relative path
	configContent := `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  k8sApp:
    manifestFile: "./manifest.yaml"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`

	configFile := filepath.Join(tempDir, "config.yaml")
	err = os.WriteFile(configFile, []byte(configContent), 0644)
	require.NoError(t, err)

	config, _, err := parser.ParseFile(configFile)
	assert.NoError(t, err)
	assert.NotNil(t, config)

	// Check that relative path was resolved to absolute
	expectedPath := filepath.Join(tempDir, "manifest.yaml")
	assert.Equal(t, expectedPath, config.Spec.K8sApp.ManifestFile)
}

func TestEdgeConnectConfig_Validate(t *testing.T) {
	tests := []struct {
		name    string
		config  EdgeConnectConfig
		wantErr bool
		errMsg  string
	}{
		{
			name: "valid config",
			config: EdgeConnectConfig{
				Kind: "edgeconnect-deployment",
				Metadata: Metadata{
					Name:         "test-app",
					AppVersion:   "1.0.0",
					Organization: "testorg",
				},
				Spec: Spec{
					DockerApp: &DockerApp{
						Image: "nginx:latest",
					},
					InfraTemplate: []InfraTemplate{
						{
							Region:       "US",
							CloudletOrg:  "TestOP",
							CloudletName: "TestCloudlet",
							FlavorName:   "small",
						},
					},
				},
			},
			wantErr: false,
		},
		{
			name: "missing kind",
			config: EdgeConnectConfig{
				Metadata: Metadata{Name: "test"},
			},
			wantErr: true,
			errMsg:  "kind is required",
		},
		{
			name: "invalid kind",
			config: EdgeConnectConfig{
				Kind:     "invalid",
				Metadata: Metadata{Name: "test"},
			},
			wantErr: true,
			errMsg:  "unsupported kind",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.config.Validate()

			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestMetadata_Validate(t *testing.T) {
	tests := []struct {
		name     string
		metadata Metadata
		wantErr  bool
		errMsg   string
	}{
		{
			name:     "valid metadata",
			metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: "testorg"},
			wantErr:  false,
		},
		{
			name:     "empty name",
			metadata: Metadata{Name: "", AppVersion: "1.0.0", Organization: "testorg"},
			wantErr:  true,
			errMsg:   "metadata.name is required",
		},
		{
			name:     "name with leading whitespace",
			metadata: Metadata{Name: "  test-app", AppVersion: "1.0.0", Organization: "testorg"},
			wantErr:  true,
			errMsg:   "cannot have leading/trailing whitespace",
		},
		{
			name:     "name with trailing whitespace",
			metadata: Metadata{Name: "test-app  ", AppVersion: "1.0.0", Organization: "testorg"},
			wantErr:  true,
			errMsg:   "cannot have leading/trailing whitespace",
		},
		{
			name:     "empty app version",
			metadata: Metadata{Name: "test-app", AppVersion: "", Organization: "testorg"},
			wantErr:  true,
			errMsg:   "metadata.appVersion is required",
		},
		{
			name:     "app version with leading whitespace",
			metadata: Metadata{Name: "test-app", AppVersion: "  1.0.0", Organization: "testorg"},
			wantErr:  true,
			errMsg:   "cannot have leading/trailing whitespace",
		},
		{
			name:     "app version with trailing whitespace",
			metadata: Metadata{Name: "test-app", AppVersion: "1.0.0  ", Organization: "testorg"},
			wantErr:  true,
			errMsg:   "cannot have leading/trailing whitespace",
		},
		{
			name:     "empty organization",
			metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: ""},
			wantErr:  true,
			errMsg:   "metadata.organization is required",
		},
		{
			name:     "organization with leading whitespace",
			metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: "  testorg"},
			wantErr:  true,
			errMsg:   "cannot have leading/trailing whitespace",
		},
		{
			name:     "organization with trailing whitespace",
			metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: "testorg  "},
			wantErr:  true,
			errMsg:   "cannot have leading/trailing whitespace",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.metadata.Validate()

			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestOutboundConnection_Validate(t *testing.T) {
	tests := []struct {
		name       string
		connection OutboundConnection
		wantErr    bool
		errMsg     string
	}{
		{
			name: "valid connection",
			connection: OutboundConnection{
				Protocol:     "tcp",
				PortRangeMin: 80,
				PortRangeMax: 80,
				RemoteCIDR:   "0.0.0.0/0",
			},
			wantErr: false,
		},
		{
			name: "missing protocol",
			connection: OutboundConnection{
				PortRangeMin: 80,
				PortRangeMax: 80,
				RemoteCIDR:   "0.0.0.0/0",
			},
			wantErr: true,
			errMsg:  "protocol is required",
		},
		{
			name: "invalid protocol",
			connection: OutboundConnection{
				Protocol:     "invalid",
				PortRangeMin: 80,
				PortRangeMax: 80,
				RemoteCIDR:   "0.0.0.0/0",
			},
			wantErr: true,
			errMsg:  "protocol must be one of: tcp, udp, icmp",
		},
		{
			name: "invalid port range min",
			connection: OutboundConnection{
				Protocol:     "tcp",
				PortRangeMin: 0,
				PortRangeMax: 80,
				RemoteCIDR:   "0.0.0.0/0",
			},
			wantErr: true,
			errMsg:  "portRangeMin must be between 1 and 65535",
		},
		{
			name: "invalid port range max",
			connection: OutboundConnection{
				Protocol:     "tcp",
				PortRangeMin: 80,
				PortRangeMax: 65536,
				RemoteCIDR:   "0.0.0.0/0",
			},
			wantErr: true,
			errMsg:  "portRangeMax must be between 1 and 65535",
		},
		{
			name: "min greater than max",
			connection: OutboundConnection{
				Protocol:     "tcp",
				PortRangeMin: 443,
				PortRangeMax: 80,
				RemoteCIDR:   "0.0.0.0/0",
			},
			wantErr: true,
			errMsg:  "portRangeMin (443) cannot be greater than portRangeMax (80)",
		},
		{
			name: "missing remote CIDR",
			connection: OutboundConnection{
				Protocol:     "tcp",
				PortRangeMin: 80,
				PortRangeMax: 80,
			},
			wantErr: true,
			errMsg:  "remoteCIDR is required",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.connection.Validate()

			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestSpec_GetMethods(t *testing.T) {
	k8sSpec := &Spec{
		K8sApp: &K8sApp{
			ManifestFile: "k8s.yaml",
		},
	}

	dockerSpec := &Spec{
		DockerApp: &DockerApp{
			ManifestFile: "docker.yaml",
		},
	}

	assert.Equal(t, "k8s.yaml", k8sSpec.GetManifestFile())
	assert.True(t, k8sSpec.IsK8sApp())
	assert.False(t, k8sSpec.IsDockerApp())

	assert.Equal(t, "docker.yaml", dockerSpec.GetManifestFile())
	assert.False(t, dockerSpec.IsK8sApp())
	assert.True(t, dockerSpec.IsDockerApp())
}

func TestReadManifestFile(t *testing.T) {
	parser := NewParser()
	tempDir := t.TempDir()

	// Create a manifest file
	manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n  name: test\n"
	manifestFile := filepath.Join(tempDir, "manifest.yaml")
	err := os.WriteFile(manifestFile, []byte(manifestContent), 0644)
	require.NoError(t, err)

	// Create config with relative path
	configContent := `
kind: edgeconnect-deployment
metadata:
  name: "test-app"
  appVersion: "1.0.0"
  organization: "testorg"
spec:
  k8sApp:
    manifestFile: "./manifest.yaml"
  infraTemplate:
    - region: "US"
      cloudletOrg: "TestOP"
      cloudletName: "TestCloudlet"
      flavorName: "small"
`

	configFile := filepath.Join(tempDir, "config.yaml")
	err = os.WriteFile(configFile, []byte(configContent), 0644)
	require.NoError(t, err)

	config, parsedManifestContent, err := parser.ParseFile(configFile)
	assert.NoError(t, err)
	assert.Equal(t, manifestContent, parsedManifestContent)
	assert.NotNil(t, config)

	// Check that relative path was resolved to absolute
	expectedPath := filepath.Join(tempDir, "manifest.yaml")
	assert.Equal(t, expectedPath, config.Spec.K8sApp.ManifestFile)
}
