package isobuilder

import (
	"fmt"
	"io"
	"os"
	"path/filepath"

	diskfs "github.com/diskfs/go-diskfs"
	"github.com/diskfs/go-diskfs/disk"
	"github.com/diskfs/go-diskfs/filesystem"
	"github.com/diskfs/go-diskfs/filesystem/iso9660"
)

// BuildFromDirectory creates an ISO 9660 image from a source directory.
// Equivalent to: genisoimage -iso-level 4 -r -J -V <label> -o <isoPath> <srcDir>
func BuildFromDirectory(srcDir, isoPath, volumeLabel string) error {
	// Step 1: Calculate total size (sum of all files + 10% overhead)
	totalSize, err := dirSize(srcDir)
	if err != nil {
		return fmt.Errorf("calculate directory size: %w", err)
	}

	// Step 2: Create disk image
	mydisk, err := diskfs.Create(isoPath, totalSize, diskfs.SectorSizeDefault)
	if err != nil {
		return fmt.Errorf("create disk image: %w", err)
	}
	mydisk.LogicalBlocksize = 2048

	// Step 3: Create ISO 9660 filesystem
	fs, err := mydisk.CreateFilesystem(disk.FilesystemSpec{
		Partition:   0,
		FSType:      filesystem.TypeISO9660,
		VolumeLabel: volumeLabel,
	})
	if err != nil {
		return fmt.Errorf("create ISO filesystem: %w", err)
	}
	defer fs.Close()

	// Step 4: Walk source dir and copy files
	err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(srcDir, path)
		if err != nil {
			return err
		}

		if relPath == "." {
			return nil // skip root
		}

		if info.IsDir() {
			if err := fs.Mkdir(relPath); err != nil {
				return fmt.Errorf("mkdir %q: %w", relPath, err)
			}
			return nil
		}

		// Regular file — copy contents
		rw, err := fs.OpenFile(relPath, os.O_CREATE|os.O_RDWR)
		if err != nil {
			return fmt.Errorf("open file %q: %w", relPath, err)
		}
		defer rw.Close()

		srcFile, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("open source %q: %w", path, err)
		}
		defer srcFile.Close()

		if _, err := io.Copy(rw, srcFile); err != nil {
			return fmt.Errorf("copy %q: %w", relPath, err)
		}

		return nil
	})
	if err != nil {
		return fmt.Errorf("walk source directory: %w", err)
	}

	// Step 5: Finalize with Rock Ridge + Joliet extensions
	iso, ok := fs.(*iso9660.FileSystem)
	if !ok {
		return fmt.Errorf("not an ISO 9660 filesystem")
	}
	if err := iso.Finalize(iso9660.FinalizeOptions{
		RockRidge:        true,
		Joliet:           true,
		DeepDirectories:  true,
		VolumeIdentifier: volumeLabel,
	}); err != nil {
		return fmt.Errorf("finalize ISO: %w", err)
	}

	return nil
}

// dirSize calculates total size of all files in a directory tree,
// with 10% overhead margin for ISO metadata.
func dirSize(dir string) (int64, error) {
	var total int64
	err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			total += info.Size()
		}
		return nil
	})
	if err != nil {
		return 0, err
	}
	// Add 10% overhead for ISO 9660 metadata
	total = total + total/10
	// Minimum 10MB (some PACS studies are small)
	if total < 10*1024*1024 {
		total = 10 * 1024 * 1024
	}
	// Round up to next 2048-byte sector
	if total%2048 != 0 {
		total = total + (2048 - total%2048)
	}
	return total, nil
}
