//go:build ignore
// +build ignore

package main

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

// FileMetadata represents metadata for embedded files
type FileMetadata struct {
	Path   string `json:"path"`
	Size   int64  `json:"size"`
	SHA256 string `json:"sha256"`
	Mode   uint32 `json:"mode"`
}

// MetadataFile contains all file metadata
type MetadataFile struct {
	Files map[string]FileMetadata `json:"files"`
}

func main() {
	linksDir := "links"

	// check if links directory exists
	if _, err := os.Stat(linksDir); os.IsNotExist(err) {
		log.Printf("Links directory '%s' not found, skipping generation", linksDir)
		return
	}

	// read all files and directories in links directory
	entries, err := os.ReadDir(linksDir)
	if err != nil {
		log.Fatal(err)
	}

	var embedFiles []string
	var fileContents = make(map[string]string)
	metadata := MetadataFile{Files: make(map[string]FileMetadata)}

	for _, entry := range entries {
		entryPathRel := filepath.Join(linksDir, entry.Name())
		entryPath, err := resolveSymlink(entryPathRel)
		if err != nil {
			log.Printf("Warning: could not resolve symlink %s: %v", entryPathRel, err)
			continue
		}

		// follow symlinks to determine actual file type
		info, err := os.Stat(entryPath)
		if err != nil {
			log.Printf("Warning: could not stat %s: %v", entryPath, err)
			continue
		}

		if info.IsDir() {
			// process directory recursively
			// resolve symlink to get real directory path
			realPath, err := evalSymlink(entryPath)
			if err != nil {
				log.Printf("Warning: could not resolve symlink %s: %v", entryPath, err)
				continue
			}

			err = filepath.Walk(realPath, func(path string, walkInfo os.FileInfo, err error) error {
				if err != nil {
					return err
				}

				// skip directories themselves, only process files
				if walkInfo.IsDir() {
					return nil
				}

				// skip system files
				if filepath.Base(path) == ".DS_Store" {
					return nil
				}

				// get relative path from real directory root
				relPathFromReal, err := filepath.Rel(realPath, path)
				if err != nil {
					return err
				}

				// construct relative path as it should appear in embedded fs
				relPath := filepath.Join(entry.Name(), relPathFromReal)

				embedFiles = append(embedFiles, relPath)
				content, fileMeta, err := readFileContentWithMetadata(path)
				if err != nil {
					return err
				}
				relPath = strings.ReplaceAll(relPath, "\\", "/")
				fileContents[relPath] = content
				fileMeta.Path = relPath
				metadata.Files[relPath] = fileMeta

				return nil
			})
			if err != nil {
				log.Fatal(err)
			}
		} else {
			// process file
			embedFiles = append(embedFiles, entry.Name())
			content, fileMeta, err := readFileContentWithMetadata(entryPath)
			if err != nil {
				log.Printf("Warning: could not read file %s: %v", entryPath, err)
				continue
			}
			fileContents[entry.Name()] = content
			fileMeta.Path = entry.Name()
			metadata.Files[entry.Name()] = fileMeta
		}
	}

	// generate Go code for embedded provider
	outputCode := `// Code generated by go generate; DO NOT EDIT.

package files

import (
	"crypto/sha256"
	"embed"
	"encoding/json"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

//go:embed fs/*
var embeddedFS embed.FS

// FileMetadata represents metadata for embedded files
type FileMetadata struct {
	Path   string ` + "`" + `json:"path"` + "`" + `
	Size   int64  ` + "`" + `json:"size"` + "`" + `
	SHA256 string ` + "`" + `json:"sha256"` + "`" + `
    Mode   uint32 ` + "`" + `json:"mode"` + "`" + `
}

// MetadataFile contains all file metadata
type MetadataFile struct {
	Files map[string]FileMetadata ` + "`" + `json:"files"` + "`" + `
}

// embeddedProvider implements EmbeddedProvider interface
type embeddedProviderImpl struct {
	metadata *MetadataFile
}

func init() {
	ep := &embeddedProviderImpl{}

	// load metadata
	if metaContent, err := embeddedFS.ReadFile("fs/.meta.json"); err == nil {
		var meta MetadataFile
		if err := json.Unmarshal(metaContent, &meta); err == nil {
			ep.metadata = &meta
		}
	}

	embeddedProvider = ep
}

// toEmbedPath converts OS-specific path to embed.FS compatible path (forward slashes)
func toEmbedPath(parts ...string) string {
	return filepath.ToSlash(filepath.Join(parts...))
}

// GetContent returns file content from embedded filesystem
func (ep *embeddedProviderImpl) GetContent(name string) ([]byte, error) {
	return embeddedFS.ReadFile(toEmbedPath("fs", name))
}

// Exists checks if file/directory exists in embedded filesystem
func (ep *embeddedProviderImpl) Exists(name string) bool {
	_, err := fs.Stat(embeddedFS, toEmbedPath("fs", name))
	return err == nil
}

// Stat returns file info from embedded filesystem
func (ep *embeddedProviderImpl) Stat(name string) (fs.FileInfo, error) {
	return fs.Stat(embeddedFS, toEmbedPath("fs", name))
}

// Copy copies file/directory from embedded FS to real filesystem
func (ep *embeddedProviderImpl) Copy(src, dst string, rewrite bool) error {
	srcPath := toEmbedPath("fs", src)
	dstPath := filepath.Join(dst, src)

	info, err := fs.Stat(embeddedFS, srcPath)
	if err != nil {
		return err
	}

	if info.IsDir() {
		return ep.copyDirFromEmbed(srcPath, dstPath, rewrite)
	}

	return ep.copyFileFromEmbed(srcPath, dstPath, rewrite)
}

// copyFileFromEmbed copies single file from embedded FS using streaming
func (ep *embeddedProviderImpl) copyFileFromEmbed(src, dst string, rewrite bool) error {
	info, err := os.Stat(dst)
	if !rewrite && info != nil && err == nil {
		return &os.PathError{Op: "copy", Path: dst, Err: os.ErrExist}
	}

	// if rewrite is true and destination is a directory, remove it to avoid errors
	if rewrite && info != nil && info.IsDir() {
		if err := os.RemoveAll(dst); err != nil {
			return err
		}
	}

	// ensure destination directory exists
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	// open embedded file for streaming
	srcFile, err := embeddedFS.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	// create destination file
	dstFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer dstFile.Close()

	// stream copy without loading full file into memory
	_, err = io.Copy(dstFile, srcFile)
	if err != nil {
		return err
	}

	// apply permissions if metadata available
	if ep.metadata != nil {
		// src has prefix "fs/"; strip to get metadata key
		// normalize path separators for metadata lookup
		rel := strings.TrimPrefix(filepath.ToSlash(src), "fs/")
		if meta, ok := ep.metadata.Files[rel]; ok {
			// best effort: try to apply permissions
			// on Windows this may not work as expected for Unix-style permissions
			// but will preserve read-only attribute
			if chmodErr := os.Chmod(dst, fs.FileMode(meta.Mode)); chmodErr != nil {
				// on Windows chmod may fail for some modes, but file is already copied
				// don't fail the entire operation
				if runtime.GOOS != "windows" {
					return chmodErr
				}
			}
		}
	}

	return nil
}

// copyDirFromEmbed copies directory recursively from embedded FS
func (ep *embeddedProviderImpl) copyDirFromEmbed(src, dst string, rewrite bool) error {
	if !rewrite {
		if _, err := os.Stat(dst); err == nil {
			return &os.PathError{Op: "copy", Path: dst, Err: os.ErrExist}
		}
	}

	return fs.WalkDir(embeddedFS, src, func(walkPath string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// embedded FS always uses forward slashes, even on Windows
		// calculate relative path: walkPath is guaranteed to start with src
		var relPath string
		if walkPath == src {
			// walking the root directory itself
			relPath = ""
		} else {
			// walkPath = "fs/dir/file.txt", src = "fs/dir" → relPath = "file.txt"
			relPath = strings.TrimPrefix(walkPath, src+"/")
		}

		// convert forward-slash path to OS-specific path for destination
		dstPath := filepath.Join(dst, filepath.FromSlash(relPath))

		if d.IsDir() {
			return os.MkdirAll(dstPath, 0755)
		}

		return ep.copyFileFromEmbed(walkPath, dstPath, rewrite)
	})
}

// List returns all embedded files with given prefix
func (ep *embeddedProviderImpl) List(prefix string) ([]string, error) {
	var files []string

	// normalize prefix to forward slashes for comparison with embedded FS paths
	normalizedPrefix := filepath.ToSlash(prefix)

	err := fs.WalkDir(embeddedFS, "fs", func(walkPath string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// skip directories
		if d.IsDir() {
			return nil
		}

		// embedded FS always uses forward slashes, even on Windows
		// walkPath is guaranteed to start with "fs/" since we're walking from "fs"
		// walkPath = "fs/dir/file.txt" → relPath = "dir/file.txt"
		relPath := strings.TrimPrefix(walkPath, "fs/")

		// check if path starts with prefix
		if normalizedPrefix == "" || strings.HasPrefix(relPath, normalizedPrefix) {
			files = append(files, relPath)
		}

		return nil
	})

	return files, err
}

// CheckHash compares file hash with embedded metadata
func (ep *embeddedProviderImpl) CheckHash(name, workingDir string) (bool, error) {
	if ep.metadata == nil {
		return false, fmt.Errorf("no metadata available")
	}

	// normalize path separators for metadata lookup
	normalizedName := filepath.ToSlash(name)
	meta, exists := ep.metadata.Files[normalizedName]
	if !exists {
		return false, fmt.Errorf("file not found in metadata")
	}

	targetPath := filepath.Join(workingDir, name)

	// check file size first (quick check)
	fsInfo, err := os.Stat(targetPath)
	if err != nil {
		return false, err
	}

	if fsInfo.Size() != meta.Size {
		return false, nil // different size, definitely different
	}

	// calculate hash of filesystem file
	fsFile, err := os.Open(targetPath)
	if err != nil {
		return false, err
	}
	defer fsFile.Close()

	hash := sha256.New()
	if _, err := io.Copy(hash, fsFile); err != nil {
		return false, err
	}

	fsHash := fmt.Sprintf("%x", hash.Sum(nil))
	return fsHash == meta.SHA256, nil
}

// ExpectedMode returns expected permission bits for a file from metadata
func (ep *embeddedProviderImpl) ExpectedMode(name string) (fs.FileMode, bool) {
	if ep.metadata == nil {
		return 0, false
	}
	// normalize path separators for metadata lookup
	normalizedName := filepath.ToSlash(name)
	meta, ok := ep.metadata.Files[normalizedName]
	if !ok {
		return 0, false
	}
	return fs.FileMode(meta.Mode), true
}
`

	outputTests := `// Code generated by go generate; DO NOT EDIT.

package files

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

// TestToEmbedPath verifies cross-platform path normalization
func TestToEmbedPath(t *testing.T) {
	tests := []struct {
		name   string
		parts  []string
		expect string
	}{
		{"simple", []string{"fs", ".env"}, "fs/.env"},
		{"nested", []string{"fs", "observability", "grafana", "config.yml"}, "fs/observability/grafana/config.yml"},
		{"single", []string{"docker-compose.yml"}, "docker-compose.yml"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := toEmbedPath(tt.parts...)
			if result != tt.expect {
				t.Errorf("toEmbedPath(%v) = %q, want %q", tt.parts, result, tt.expect)
			}
			// verify no backslashes (Windows compatibility)
			if strings.Contains(result, "\\") {
				t.Errorf("toEmbedPath() returned path with backslash: %q", result)
			}
		})
	}
}

// TestEmbeddedProvider_PathNormalization tests that embedded provider works with both "/" and "\" in input
func TestEmbeddedProvider_PathNormalization(t *testing.T) {
	if embeddedProvider == nil {
		t.Skip("embedded provider not available")
	}

	// test with known embedded file
	testCases := []struct {
		name     string
		path     string
		wantFile string // expected file in embedded FS
	}{
		{"unix_style", ".env", ".env"},
		{"unix_nested", "observability/grafana/config/grafana.ini", "observability/grafana/config/grafana.ini"},
		{"windows_style", filepath.Join("observability", "grafana", "config", "grafana.ini"), "observability/grafana/config/grafana.ini"},
		{"mixed_depth", filepath.Join("providers-configs", "deepseek.provider.yml"), "providers-configs/deepseek.provider.yml"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// test Exists
			if !embeddedProvider.Exists(tc.path) {
				t.Errorf("Exists(%q) = false, want true", tc.path)
			}

			// test GetContent
			content, err := embeddedProvider.GetContent(tc.path)
			if err != nil {
				t.Errorf("GetContent(%q) error = %v", tc.path, err)
			}
			if len(content) == 0 {
				t.Errorf("GetContent(%q) returned empty content", tc.path)
			}

			// test Stat
			info, err := embeddedProvider.Stat(tc.path)
			if err != nil {
				t.Errorf("Stat(%q) error = %v", tc.path, err)
			}
			if info.Size() != int64(len(content)) {
				t.Errorf("Stat(%q).Size() = %d, want %d", tc.path, info.Size(), len(content))
			}
		})
	}
}

// TestEmbeddedProvider_CheckHash tests hash verification with normalized paths
func TestEmbeddedProvider_CheckHash(t *testing.T) {
	if embeddedProvider == nil {
		t.Skip("embedded provider not available")
	}

	workingDir := t.TempDir()

	testFiles := []string{
		".env",
		filepath.Join("observability", "loki", "config.yml"),
		filepath.Join("providers-configs", "deepseek.provider.yml"),
	}

	for _, testFile := range testFiles {
		t.Run(testFile, func(t *testing.T) {
			// copy file to working directory
			err := embeddedProvider.Copy(testFile, workingDir, false)
			if err != nil {
				t.Fatalf("Copy(%q) error = %v", testFile, err)
			}

			// verify hash matches
			match, err := embeddedProvider.CheckHash(testFile, workingDir)
			if err != nil {
				t.Errorf("CheckHash(%q) error = %v", testFile, err)
			}
			if !match {
				t.Errorf("CheckHash(%q) = false, want true for just copied file", testFile)
			}

			// verify ExpectedMode works
			if mode, ok := embeddedProvider.ExpectedMode(testFile); !ok {
				t.Errorf("ExpectedMode(%q) not found in metadata", testFile)
			} else if mode == 0 {
				t.Errorf("ExpectedMode(%q) = 0, want non-zero", testFile)
			}
		})
	}
}

// TestEmbeddedProvider_Copy tests directory and file copying with path normalization
func TestEmbeddedProvider_Copy(t *testing.T) {
	if embeddedProvider == nil {
		t.Skip("embedded provider not available")
	}

	workingDir := t.TempDir()

	testCases := []struct {
		name      string
		src       string
		expectMin int // minimum files expected
	}{
		{"single_file", ".env", 1},
		{"nested_file", filepath.Join("observability", "loki", "config.yml"), 1},
		{"directory", filepath.Join("observability", "loki"), 1},
		{"deep_directory", filepath.Join("observability", "grafana", "dashboards"), 3},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			dstDir := filepath.Join(workingDir, tc.name)
			err := embeddedProvider.Copy(tc.src, dstDir, false)
			if err != nil {
				t.Fatalf("Copy(%q) error = %v", tc.src, err)
			}

			// verify file/directory exists at destination
			dstPath := filepath.Join(dstDir, tc.src)
			info, err := os.Stat(dstPath)
			if err != nil {
				t.Fatalf("Stat(%q) after copy error = %v", dstPath, err)
			}

			// for files, verify content matches
			if !info.IsDir() {
				embeddedContent, _ := embeddedProvider.GetContent(tc.src)
				copiedContent, _ := os.ReadFile(dstPath)
				if string(embeddedContent) != string(copiedContent) {
					t.Errorf("copied file content differs from embedded")
				}
			} else {
				// for directories, count files
				var fileCount int
				filepath.Walk(dstPath, func(path string, info os.FileInfo, err error) error {
					if err == nil && !info.IsDir() {
						fileCount++
					}
					return nil
				})
				if fileCount < tc.expectMin {
					t.Errorf("copied directory has %d files, want at least %d", fileCount, tc.expectMin)
				}
			}
		})
	}
}

// TestEmbeddedProvider_List tests listing with various prefix formats
func TestEmbeddedProvider_List(t *testing.T) {
	if embeddedProvider == nil {
		t.Skip("embedded provider not available")
	}

	testCases := []struct {
		name        string
		prefix      string
		expectMin   int      // minimum files expected
		mustHave    []string // paths that must be in results (normalized)
		mustNotHave []string // paths that must not be in results
	}{
		{
			name:      "unix_style_prefix",
			prefix:    "observability/loki",
			expectMin: 1,
			mustHave:  []string{"observability/loki/config.yml"},
		},
		{
			name:      "windows_style_prefix",
			prefix:    filepath.Join("observability", "grafana"),
			expectMin: 5,
			mustHave:  []string{"observability/grafana/config/grafana.ini"},
		},
		{
			name:        "providers_prefix",
			prefix:      "providers-configs",
			expectMin:   5,
			mustHave:    []string{"providers-configs/deepseek.provider.yml"},
			mustNotHave: []string{"observability/loki/config.yml"},
		},
		{
			name:      "empty_prefix",
			prefix:    "",
			expectMin: 20, // should return all files
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			files, err := embeddedProvider.List(tc.prefix)
			if err != nil {
				t.Fatalf("List(%q) error = %v", tc.prefix, err)
			}

			if len(files) < tc.expectMin {
				t.Errorf("List(%q) returned %d files, want at least %d", tc.prefix, len(files), tc.expectMin)
			}

			// verify all returned paths use forward slashes
			for _, f := range files {
				if strings.Contains(f, "\\") {
					t.Errorf("List(%q) returned path with backslash: %q", tc.prefix, f)
				}
			}

			// verify must-have files are present
			fileSet := make(map[string]bool)
			for _, f := range files {
				fileSet[f] = true
			}

			for _, mustHave := range tc.mustHave {
				if !fileSet[mustHave] {
					t.Errorf("List(%q) missing expected file: %q", tc.prefix, mustHave)
				}
			}

			// verify must-not-have files are absent
			for _, mustNotHave := range tc.mustNotHave {
				if fileSet[mustNotHave] {
					t.Errorf("List(%q) contains unexpected file: %q", tc.prefix, mustNotHave)
				}
			}
		})
	}
}

// TestEmbeddedProvider_PermissionsPreserved tests that file permissions are preserved on copy
func TestEmbeddedProvider_PermissionsPreserved(t *testing.T) {
	if embeddedProvider == nil {
		t.Skip("embedded provider not available")
	}

	workingDir := t.TempDir()

	// find an executable file in embedded FS
	executableFiles := []string{
		"observability/jaeger/bin/jaeger-clickhouse-linux-amd64",
		"observability/jaeger/bin/jaeger-clickhouse-linux-arm64",
	}

	for _, testFile := range executableFiles {
		if !embeddedProvider.Exists(testFile) {
			continue
		}

		t.Run(testFile, func(t *testing.T) {
			expectedMode, ok := embeddedProvider.ExpectedMode(testFile)
			if !ok {
				t.Skip("no mode metadata available")
			}

			err := embeddedProvider.Copy(testFile, workingDir, false)
			if err != nil {
				t.Fatalf("Copy(%q) error = %v", testFile, err)
			}

			copiedPath := filepath.Join(workingDir, testFile)
			info, err := os.Stat(copiedPath)
			if err != nil {
				t.Fatalf("Stat(%q) error = %v", copiedPath, err)
			}

			if info.Mode().Perm() != expectedMode.Perm() {
				t.Errorf("copied file mode = %o, want %o", info.Mode().Perm(), expectedMode.Perm())
			}
		})

		break // test only one executable file
	}
}
`

	// create fs directory
	err = os.MkdirAll("fs", 0755)
	if err != nil {
		log.Fatal(err)
	}

	// copy file contents to fs directory
	for filename, content := range fileContents {
		destPath := filepath.Join("fs", filename)

		// create directories if needed
		err = os.MkdirAll(filepath.Dir(destPath), 0755)
		if err != nil {
			log.Fatal(err)
		}

		err = os.WriteFile(destPath, []byte(content), 0644)
		if err != nil {
			log.Fatal(err)
		}

		// apply source file permissions if present
		if meta, ok := metadata.Files[filename]; ok {
			if chmodErr := os.Chmod(destPath, os.FileMode(meta.Mode)); chmodErr != nil {
				log.Fatal(chmodErr)
			}
		}
	}

	// write metadata file
	metadataContent, err := json.MarshalIndent(metadata, "", "  ")
	if err != nil {
		log.Fatal(err)
	}

	metaPath := filepath.Join("fs", ".meta.json")
	err = os.WriteFile(metaPath, metadataContent, 0644)
	if err != nil {
		log.Fatal(err)
	}

	// write generated Go code
	err = os.WriteFile("fs.go", []byte(outputCode), 0644)
	if err != nil {
		log.Fatal(err)
	}

	err = os.WriteFile("fs_test.go", []byte(outputTests), 0644)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Generated embedded files for: %v\n", embedFiles)
}

func readSymlinkWindows(symlinkPath string) (string, error) {
	fileContent, err := os.ReadFile(symlinkPath)
	if err != nil {
		return "", fmt.Errorf("failed to read symlink on windows")
	}

	content := strings.Split(string(fileContent), "\n")[0]
	if contentLen := len(content); contentLen > 255 || contentLen == 0 {
		return "", fmt.Errorf("invalid symlink path")
	}

	content = strings.ReplaceAll(content, "\\", "/")
	content = filepath.Join(filepath.Dir(symlinkPath), content)

	return filepath.Abs(content)
}

func resolveSymlink(entryPath string) (string, error) {
	if runtime.GOOS == "windows" {
		return readSymlinkWindows(entryPath)
	}

	return entryPath, nil
}

func evalSymlink(entryPath string) (string, error) {
	if runtime.GOOS == "windows" {
		return filepath.Abs(entryPath)
	}

	return filepath.EvalSymlinks(entryPath)
}

func readFileContentWithMetadata(filename string) (string, FileMetadata, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", FileMetadata{}, err
	}
	defer file.Close()

	// get file info for size
	info, err := file.Stat()
	if err != nil {
		return "", FileMetadata{}, err
	}

	// calculate hash while reading content
	hash := sha256.New()
	teeReader := io.TeeReader(file, hash)

	content, err := io.ReadAll(teeReader)
	if err != nil {
		return "", FileMetadata{}, err
	}

	meta := FileMetadata{
		Size:   info.Size(),
		SHA256: fmt.Sprintf("%x", hash.Sum(nil)),
		Mode:   uint32(info.Mode().Perm()),
	}

	return string(content), meta, nil
}
