package spitfire

import (
	"archive/tar"
	"crypto/rand"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"encoding/hex"
)

// Config struct to hold SourceForge configurations
type Config struct {
	SFKeyPath string
	SFUser    string
	SFHost    string
	SFProject string
}

// Load the SourceForge configuration from a file
func LoadConfig() (*Config, error) {
	file, err := os.Open("sourceforge_config.json") // Assuming a JSON config file
	if err != nil {
		return nil, err
	}
	defer file.Close()

	config := &Config{}
	if err := json.NewDecoder(file).Decode(config); err != nil {
		return nil, err
	}

	return config, nil
}

// CompressDirectory compresses the build directory to a tar.gz file using PAX format for large file support
func CompressDirectory(srcDir, dstFile string) error {
	// Create the destination file
	f, err := os.Create(dstFile)
	if err != nil {
		return fmt.Errorf("could not create file %s: %v", dstFile, err)
	}
	defer f.Close()

	// Create a new gzip writer
	gw := gzip.NewWriter(f)
	defer gw.Close()

	// Create a new tar writer with PAX format for large file support
	tw := tar.NewWriter(gw)
	defer tw.Close()

	// Walk through the source directory and add files to the tar archive
	err = filepath.Walk(srcDir, func(file string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Create tar header using PAX format
		header, err := tar.FileInfoHeader(fi, "")
		if err != nil {
			return err
		}

		// Set the correct header name, preserving the relative directory structure
		relPath, err := filepath.Rel(srcDir, file)
		if err != nil {
			return err
		}
		header.Name = relPath

		// Explicitly set the type flag for directories
		if fi.IsDir() {
			header.Typeflag = tar.TypeDir
		} else if fi.Mode()&os.ModeSymlink != 0 {
			// Handle symlinks
			linkTarget, err := os.Readlink(file)
			if err != nil {
				return err
			}
			header.Linkname = linkTarget
		}

		// Write the header to the tarball
		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		// If it's a directory or symlink, skip writing its contents
		if fi.IsDir() || fi.Mode()&os.ModeSymlink != 0 {
			return nil
		}

		// Open the file for reading
		f, err := os.Open(file)
		if err != nil {
			return err
		}
		defer f.Close()

		// Copy the file content to the tar writer
		if _, err := io.Copy(tw, f); err != nil {
			return err
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("error walking the source directory %s: %v", srcDir, err)
	}

	return nil
}

// Upload the file to SourceForge, ensuring the local directory structure is created and uploaded
func Upload(config *Config, buildPath, remoteDir string) error {
    // Generate a random hash for the temp directory name
    randomHash, err := generateRandomHash(8) // 8 bytes = 16 hex characters
    if err != nil {
        return fmt.Errorf("failed to generate random hash: %v", err)
    }

    // Create a temporary directory with the random hash appended
    tmpDir, err := os.MkdirTemp("", "spitfire-upload-"+randomHash)
    if err != nil {
        return fmt.Errorf("failed to create temporary directory: %v", err)
    }

    // Create the required local directory structure inside the temporary directory
    localDir := filepath.Join(tmpDir, remoteDir)
    err = os.MkdirAll(localDir, os.ModePerm)
    if err != nil {
        return fmt.Errorf("failed to create local directory structure: %v", err)
    }

    // Move the build file to the local directory structure
    destinationFile := filepath.Join(localDir, filepath.Base(buildPath))
    err = copyFile(buildPath, destinationFile)
    if err != nil {
        return fmt.Errorf("failed to copy file to local directory structure: %v", err)
    }

    // Upload the entire local directory structure to the remote directory
    fmt.Printf("Uploading file %s to %s on SourceForge...\n", buildPath, remoteDir)
    scpCmd := exec.Command("scp", "-i", config.SFKeyPath, "-r", tmpDir+"/.", fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, "/"))
    scpCmd.Stdout = os.Stdout
    scpCmd.Stderr = os.Stderr
    return scpCmd.Run()
}

// Helper function to generate a random hash
func generateRandomHash(length int) (string, error) {
    bytes := make([]byte, length)
    _, err := rand.Read(bytes)
    if err != nil {
        return "", err
    }
    return hex.EncodeToString(bytes), nil
}

// Helper function to copy a file from src to dst
func copyFile(src, dst string) error {
    sourceFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer sourceFile.Close()

    destFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer destFile.Close()

    _, err = io.Copy(destFile, sourceFile)
    if err != nil {
        return err
    }

    return destFile.Sync() // Ensure all writes to the file are flushed
}

// Download the APPINDEX file from SourceForge
func DownloadAPPINDEX(config *Config, remoteDir string) error {
	fmt.Println("Downloading APPINDEX from SourceForge...")

	// Construct the correct path without double slashes
	remoteAPPINDEXPath := filepath.Join(remoteDir, "APPINDEX")

	// Run the SCP command to download the APPINDEX file
	cmd := exec.Command("scp", "-i", config.SFKeyPath, fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, remoteAPPINDEXPath), "./APPINDEX")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Run()
	if err != nil {
		// Check if the error is due to the file not existing
		if strings.Contains(err.Error(), "No such file or directory") {
			fmt.Println("APPINDEX file not found on the server. A new one will be created.")
			return nil // Continue without failing if the APPINDEX is missing
		}
		return fmt.Errorf("failed to download APPINDEX: %v", err) // Fail for other types of errors
	}

	fmt.Println("APPINDEX downloaded successfully.")
	return nil
}

// Upload the updated APPINDEX file to SourceForge
func UploadAPPINDEX(config *Config, remoteDir string) error {
	fmt.Println("Uploading updated APPINDEX to SourceForge...")
	cmd := exec.Command("scp", "-i", config.SFKeyPath, "./APPINDEX", fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, remoteDir))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// GetDirectorySize calculates the total size of all files in a directory
func GetDirectorySize(path string) (int64, error) {
	var size int64
	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			size += info.Size()
		}
		return nil
	})
	return size, err
}

// GetFileSize returns the size of a file in bytes
func GetFileSize(filePath string) (int64, error) {
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		return 0, err
	}
	return fileInfo.Size(), nil
}