package main

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

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

	"debian-bootc-image-builder/internal/apt"
	"debian-bootc-image-builder/internal/config"
)

func TestManifestConfig(t *testing.T) {
	// Test creating a new ManifestConfig
	config := &ManifestConfig{
		ImageName:      "test-image",
		ImageTypes:     []string{"qcow2"},
		TargetArch:     "amd64",
		RootfsType:     "ext4",
		DistroDefPath:  []string{"./data/defs"},
		BuildContainer: "debian:trixie",
	}
	
	assert.Equal(t, "test-image", config.ImageName)
	assert.Equal(t, []string{"qcow2"}, config.ImageTypes)
	assert.Equal(t, "amd64", config.TargetArch)
	assert.Equal(t, "ext4", config.RootfsType)
	assert.Equal(t, []string{"./data/defs"}, config.DistroDefPath)
	assert.Equal(t, "debian:trixie", config.BuildContainer)
}

func TestManifestConfigLoadPackageDefinitions(t *testing.T) {
	// Create a temporary directory with test package definitions
	tmpDir := t.TempDir()
	defsDir := filepath.Join(tmpDir, "defs")
	err := os.MkdirAll(defsDir, 0755)
	require.NoError(t, err)
	
	// Create a test package definition file
	defFile := filepath.Join(defsDir, "debian-trixie.yaml")
	defContent := `qcow2:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - bootc
    - apt-ostree

ami:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - cloud-guest-utils
    - bootc
    - apt-ostree

vmdk:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - open-vm-tools
    - bootc
    - apt-ostree

debian-installer:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - debian-installer
    - bootc
    - apt-ostree

calamares:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - calamares
    - bootc
    - apt-ostree
`
	
	err = os.WriteFile(defFile, []byte(defContent), 0644)
	require.NoError(t, err)
	
	// Test loading package definitions
	config := &ManifestConfig{
		ImageTypes:    []string{"qcow2"},
		DistroDefPath: []string{defsDir},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.NoError(t, err)
	assert.NotEmpty(t, packages)
	assert.Contains(t, packages, "base-files")
	assert.Contains(t, packages, "systemd")
	assert.Contains(t, packages, "linux-image-amd64")
	assert.Contains(t, packages, "grub-common")
	assert.Contains(t, packages, "bootc")
	assert.Contains(t, packages, "apt-ostree")
}

func TestManifestConfigLoadPackageDefinitionsAMI(t *testing.T) {
	// Create a temporary directory with test package definitions
	tmpDir := t.TempDir()
	defsDir := filepath.Join(tmpDir, "defs")
	err := os.MkdirAll(defsDir, 0755)
	require.NoError(t, err)
	
	// Create a test package definition file
	defFile := filepath.Join(defsDir, "debian-trixie.yaml")
	defContent := `qcow2:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - bootc
    - apt-ostree

ami:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - cloud-guest-utils
    - bootc
    - apt-ostree
`
	
	err = os.WriteFile(defFile, []byte(defContent), 0644)
	require.NoError(t, err)
	
	// Test loading AMI package definitions
	config := &ManifestConfig{
		ImageTypes:    []string{"ami"},
		DistroDefPath: []string{defsDir},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.NoError(t, err)
	assert.NotEmpty(t, packages)
	assert.Contains(t, packages, "base-files")
	assert.Contains(t, packages, "systemd")
	assert.Contains(t, packages, "linux-image-amd64")
	assert.Contains(t, packages, "grub-common")
	assert.Contains(t, packages, "cloud-guest-utils")
	assert.Contains(t, packages, "bootc")
	assert.Contains(t, packages, "apt-ostree")
}

func TestManifestConfigLoadPackageDefinitionsVMDK(t *testing.T) {
	// Create a temporary directory with test package definitions
	tmpDir := t.TempDir()
	defsDir := filepath.Join(tmpDir, "defs")
	err := os.MkdirAll(defsDir, 0755)
	require.NoError(t, err)
	
	// Create a test package definition file
	defFile := filepath.Join(defsDir, "debian-trixie.yaml")
	defContent := `qcow2:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - bootc
    - apt-ostree

vmdk:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - open-vm-tools
    - bootc
    - apt-ostree
`
	
	err = os.WriteFile(defFile, []byte(defContent), 0644)
	require.NoError(t, err)
	
	// Test loading VMDK package definitions
	config := &ManifestConfig{
		ImageTypes:    []string{"vmdk"},
		DistroDefPath: []string{defsDir},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.NoError(t, err)
	assert.NotEmpty(t, packages)
	assert.Contains(t, packages, "base-files")
	assert.Contains(t, packages, "systemd")
	assert.Contains(t, packages, "linux-image-amd64")
	assert.Contains(t, packages, "grub-common")
	assert.Contains(t, packages, "open-vm-tools")
	assert.Contains(t, packages, "bootc")
	assert.Contains(t, packages, "apt-ostree")
}

func TestManifestConfigLoadPackageDefinitionsDebianInstaller(t *testing.T) {
	// Create a temporary directory with test package definitions
	tmpDir := t.TempDir()
	defsDir := filepath.Join(tmpDir, "defs")
	err := os.MkdirAll(defsDir, 0755)
	require.NoError(t, err)
	
	// Create a test package definition file
	defFile := filepath.Join(defsDir, "debian-trixie.yaml")
	defContent := `qcow2:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - bootc
    - apt-ostree

debian-installer:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - debian-installer
    - bootc
    - apt-ostree
`
	
	err = os.WriteFile(defFile, []byte(defContent), 0644)
	require.NoError(t, err)
	
	// Test loading debian-installer package definitions
	config := &ManifestConfig{
		ImageTypes:    []string{"debian-installer"},
		DistroDefPath: []string{defsDir},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.NoError(t, err)
	assert.NotEmpty(t, packages)
	assert.Contains(t, packages, "base-files")
	assert.Contains(t, packages, "systemd")
	assert.Contains(t, packages, "linux-image-amd64")
	assert.Contains(t, packages, "debian-installer")
	assert.Contains(t, packages, "bootc")
	assert.Contains(t, packages, "apt-ostree")
}

func TestManifestConfigLoadPackageDefinitionsCalamares(t *testing.T) {
	// Create a temporary directory with test package definitions
	tmpDir := t.TempDir()
	defsDir := filepath.Join(tmpDir, "defs")
	err := os.MkdirAll(defsDir, 0755)
	require.NoError(t, err)
	
	// Create a test package definition file
	defFile := filepath.Join(defsDir, "debian-trixie.yaml")
	defContent := `qcow2:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - bootc
    - apt-ostree

calamares:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - calamares
    - bootc
    - apt-ostree
`
	
	err = os.WriteFile(defFile, []byte(defContent), 0644)
	require.NoError(t, err)
	
	// Test loading calamares package definitions
	config := &ManifestConfig{
		ImageTypes:    []string{"calamares"},
		DistroDefPath: []string{defsDir},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.NoError(t, err)
	assert.NotEmpty(t, packages)
	assert.Contains(t, packages, "base-files")
	assert.Contains(t, packages, "systemd")
	assert.Contains(t, packages, "linux-image-amd64")
	assert.Contains(t, packages, "calamares")
	assert.Contains(t, packages, "bootc")
	assert.Contains(t, packages, "apt-ostree")
}

func TestManifestConfigLoadPackageDefinitionsNotFound(t *testing.T) {
	// Test loading package definitions from non-existent directory
	config := &ManifestConfig{
		ImageTypes:    []string{"qcow2"},
		DistroDefPath: []string{"/non/existent/path"},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.Error(t, err)
	assert.Empty(t, packages)
}

func TestManifestConfigLoadPackageDefinitionsInvalidYAML(t *testing.T) {
	// Create a temporary directory with invalid YAML
	tmpDir := t.TempDir()
	defsDir := filepath.Join(tmpDir, "defs")
	err := os.MkdirAll(defsDir, 0755)
	require.NoError(t, err)
	
	// Create an invalid YAML file
	defFile := filepath.Join(defsDir, "debian-trixie.yaml")
	invalidContent := `qcow2:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - bootc
    - apt-ostree

ami:
  packages:
    - base-files
    - systemd
    - linux-image-amd64
    - grub-common
    - cloud-guest-utils
    - bootc
    - apt-ostree
`
	
	err = os.WriteFile(defFile, []byte(invalidContent), 0644)
	require.NoError(t, err)
	
	// Test loading package definitions
	config := &ManifestConfig{
		ImageTypes:    []string{"qcow2"},
		DistroDefPath: []string{defsDir},
	}
	
	packages, err := config.loadPackageDefinitions()
	assert.NoError(t, err)
	assert.NotEmpty(t, packages)
}

func TestMakeManifest(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Create a test configuration
	config := &ManifestConfig{
		ImageName:      "test-image",
		ImageTypes:     []string{"qcow2"},
		TargetArch:     "amd64",
		RootfsType:     "ext4",
		DistroDefPath:  []string{"./data/defs"},
		BuildContainer: "debian:trixie",
	}
	
	// Create a test solver
	solver := apt.NewSolver()
	
	// Test manifest generation
	manifest, repos, err := makeManifest(config, solver, tmpDir)
	assert.NoError(t, err)
	assert.NotNil(t, manifest)
	assert.NotNil(t, repos)
	
	// Test manifest structure
	assert.Contains(t, manifest, "version")
	assert.Contains(t, manifest, "pipelines")
	
	// Test that version is "2"
	assert.Equal(t, "2", manifest["version"])
	
	// Test that pipelines is a slice
	pipelines, ok := manifest["pipelines"].([]map[string]interface{})
	assert.True(t, ok)
	assert.Len(t, pipelines, 2)
	
	// Test build pipeline
	buildPipeline := pipelines[0]
	assert.Equal(t, "build", buildPipeline["name"])
	assert.Equal(t, "org.osbuild.linux", buildPipeline["runner"])
	
	// Test image pipeline
	imagePipeline := pipelines[1]
	assert.Equal(t, "image", imagePipeline["name"])
	assert.Equal(t, "org.osbuild.linux", imagePipeline["runner"])
}

func TestMakeManifestWithDifferentImageTypes(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Test with different image types
	imageTypes := []string{"qcow2", "ami", "vmdk", "debian-installer", "calamares"}
	
	for _, imageType := range imageTypes {
		t.Run(imageType, func(t *testing.T) {
			config := &ManifestConfig{
				ImageName:      "test-image",
				ImageTypes:     []string{imageType},
				TargetArch:     "amd64",
				RootfsType:     "ext4",
				DistroDefPath:  []string{"./data/defs"},
				BuildContainer: "debian:trixie",
			}
			
			solver := apt.NewSolver()
			
			manifest, repos, err := makeManifest(config, solver, tmpDir)
			assert.NoError(t, err)
			assert.NotNil(t, manifest)
			assert.NotNil(t, repos)
			
			// Test manifest structure
			assert.Contains(t, manifest, "version")
			assert.Contains(t, manifest, "pipelines")
			assert.Equal(t, "2", manifest["version"])
			
			// Test that pipelines is a slice
			pipelines, ok := manifest["pipelines"].([]map[string]interface{})
			assert.True(t, ok)
			assert.Len(t, pipelines, 2)
		})
	}
}

func TestMakeManifestWithDifferentArchitectures(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Test with different architectures
	architectures := []string{"amd64", "arm64", "ppc64le", "s390x"}
	
	for _, arch := range architectures {
		t.Run(arch, func(t *testing.T) {
			config := &ManifestConfig{
				ImageName:      "test-image",
				ImageTypes:     []string{"qcow2"},
				TargetArch:     arch,
				RootfsType:     "ext4",
				DistroDefPath:  []string{"./data/defs"},
				BuildContainer: "debian:trixie",
			}
			
			solver := apt.NewSolver()
			
			manifest, repos, err := makeManifest(config, solver, tmpDir)
			assert.NoError(t, err)
			assert.NotNil(t, manifest)
			assert.NotNil(t, repos)
			
			// Test manifest structure
			assert.Contains(t, manifest, "version")
			assert.Contains(t, manifest, "pipelines")
			assert.Equal(t, "2", manifest["version"])
		})
	}
}

func TestMakeManifestWithDifferentRootfsTypes(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Test with different rootfs types
	rootfsTypes := []string{"ext4", "xfs", "btrfs"}
	
	for _, rootfsType := range rootfsTypes {
		t.Run(rootfsType, func(t *testing.T) {
			config := &ManifestConfig{
				ImageName:      "test-image",
				ImageTypes:     []string{"qcow2"},
				TargetArch:     "amd64",
				RootfsType:     rootfsType,
				DistroDefPath:  []string{"./data/defs"},
				BuildContainer: "debian:trixie",
			}
			
			solver := apt.NewSolver()
			
			manifest, repos, err := makeManifest(config, solver, tmpDir)
			assert.NoError(t, err)
			assert.NotNil(t, manifest)
			assert.NotNil(t, repos)
			
			// Test manifest structure
			assert.Contains(t, manifest, "version")
			assert.Contains(t, manifest, "pipelines")
			assert.Equal(t, "2", manifest["version"])
		})
	}
}

func TestMakeManifestErrorHandling(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Test with invalid configuration
	config := &ManifestConfig{
		ImageName:      "test-image",
		ImageTypes:     []string{"qcow2"},
		TargetArch:     "amd64",
		RootfsType:     "ext4",
		DistroDefPath:  []string{"/non/existent/path"},
		BuildContainer: "debian:trixie",
	}
	
	solver := apt.NewSolver()
	
	// Test manifest generation with invalid distro def path
	manifest, repos, err := makeManifest(config, solver, tmpDir)
	assert.Error(t, err)
	assert.Nil(t, manifest)
	assert.Nil(t, repos)
}

func TestMakeManifestWithEmptyImageTypes(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Test with empty image types
	config := &ManifestConfig{
		ImageName:      "test-image",
		ImageTypes:     []string{},
		TargetArch:     "amd64",
		RootfsType:     "ext4",
		DistroDefPath:  []string{"./data/defs"},
		BuildContainer: "debian:trixie",
	}
	
	solver := apt.NewSolver()
	
	// Test manifest generation with empty image types
	manifest, repos, err := makeManifest(config, solver, tmpDir)
	assert.Error(t, err)
	assert.Nil(t, manifest)
	assert.Nil(t, repos)
}

func TestMakeManifestWithNilSolver(t *testing.T) {
	// Create a temporary directory for testing
	tmpDir := t.TempDir()
	
	// Test with nil solver
	config := &ManifestConfig{
		ImageName:      "test-image",
		ImageTypes:     []string{"qcow2"},
		TargetArch:     "amd64",
		RootfsType:     "ext4",
		DistroDefPath:  []string{"./data/defs"},
		BuildContainer: "debian:trixie",
	}
	
	// Test manifest generation with nil solver
	manifest, repos, err := makeManifest(config, nil, tmpDir)
	assert.Error(t, err)
	assert.Nil(t, manifest)
	assert.Nil(t, repos)
}
