package main

import (
	"fmt"
	"io"
	"ldiff-converter/pb"
	"os"
	"path/filepath"

	"golang.org/x/exp/mmap"
)

func LDiffFile(data *pb.AssetManifest, assetName string, assetSize int64, ldiffsDir, outputDir string) error {
	path := filepath.Join(ldiffsDir, data.ChunkFileName)

	info, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("%s does not exist: %w", path, err)
	}
	fileSize := info.Size()

	var buffer []byte

	if fileSize > 10*1024*1024 && data.HdiffFileSize > 1*1024*1024 {
		// ưu tiên mmap cho file lớn
		reader, err := mmap.Open(path)
		if err == nil {
			defer reader.Close()
			buffer = make([]byte, data.HdiffFileSize)
			n, err := reader.ReadAt(buffer, data.HdiffFileInChunkOffset)
			if err != nil && err != io.EOF {
				return fmt.Errorf("error reading mmap data: %w", err)
			}
			if int64(n) < data.HdiffFileSize {
				return fmt.Errorf("expected %d bytes, but read %d bytes", data.HdiffFileSize, n)
			}
		} else {
			// fallback sang buffered read
			file, err := os.Open(path)
			if err != nil {
				return fmt.Errorf("error opening file %s: %w", path, err)
			}
			defer file.Close()
			buffer, err = ReadBuffer(file, data.HdiffFileInChunkOffset, data.HdiffFileSize)
			if err != nil {
				return err
			}
		}
	} else {
		// file nhỏ, dùng buffered read
		file, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("error opening file %s: %w", path, err)
		}
		defer file.Close()
		buffer, err = ReadBuffer(file, data.HdiffFileInChunkOffset, data.HdiffFileSize)
		if err != nil {
			return err
		}
	}

	extension := ""
	if data.OriginalFileSize > 0 && assetSize != data.HdiffFileSize {
		extension = ".hdiff"
	}
	assetPath := filepath.Join(outputDir, assetName+extension)
	parentDir := filepath.Dir(assetPath)
	if _, err := os.Stat(parentDir); os.IsNotExist(err) {
		if err := os.MkdirAll(parentDir, 0o755); err != nil {
			return fmt.Errorf("error creating directory %s: %w", parentDir, err)
		}
	}

	if err := os.WriteFile(assetPath, buffer, 0o644); err != nil {
		return fmt.Errorf("error writing file %s: %w", assetPath, err)
	}

	return nil
}

func ReadBuffer(file *os.File, offset int64, size int64) ([]byte, error) {
	buffer := make([]byte, size)

	n, err := file.ReadAt(buffer, offset)
	if err != nil && err != io.EOF {
		return nil, fmt.Errorf("error reading data: %w", err)
	}
	if int64(n) < size {
		return nil, fmt.Errorf("expected %d bytes, but read %d bytes", size, n)
	}

	return buffer, nil
}
