package mfer

import (
	"crypto/sha256"
	"io"
	"sync"
	"time"

	"github.com/multiformats/go-multihash"
)

// FileProgress is called during file processing to report bytes read.
type FileProgress func(bytesRead int64)

// ManifestBuilder constructs a manifest by adding files one at a time.
type ManifestBuilder struct {
	mu        sync.Mutex
	files     []*MFFilePath
	createdAt time.Time
}

// NewBuilder creates a new ManifestBuilder.
func NewBuilder() *ManifestBuilder {
	return &ManifestBuilder{
		files:     make([]*MFFilePath, 0),
		createdAt: time.Now(),
	}
}

// AddFile reads file content from reader, computes hashes, and adds to manifest.
// The progress callback is called periodically with total bytes read so far.
// Returns the number of bytes read.
func (b *ManifestBuilder) AddFile(
	path string,
	size int64,
	mtime time.Time,
	reader io.Reader,
	progress FileProgress,
) (int64, error) {
	// Create hash writer
	h := sha256.New()

	// Read file in chunks, updating hash and progress
	var totalRead int64
	buf := make([]byte, 64*1024) // 64KB chunks

	for {
		n, err := reader.Read(buf)
		if n > 0 {
			h.Write(buf[:n])
			totalRead += int64(n)
			if progress != nil {
				progress(totalRead)
			}
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return totalRead, err
		}
	}

	// Encode hash as multihash (SHA2-256)
	mh, err := multihash.Encode(h.Sum(nil), multihash.SHA2_256)
	if err != nil {
		return totalRead, err
	}

	// Create file entry
	entry := &MFFilePath{
		Path: path,
		Size: size,
		Hashes: []*MFFileChecksum{
			{MultiHash: mh},
		},
		Mtime: newTimestampFromTime(mtime),
	}

	b.mu.Lock()
	b.files = append(b.files, entry)
	b.mu.Unlock()

	return totalRead, nil
}

// FileCount returns the number of files added to the builder.
func (b *ManifestBuilder) FileCount() int {
	b.mu.Lock()
	defer b.mu.Unlock()
	return len(b.files)
}

// Build finalizes the manifest and writes it to the writer.
func (b *ManifestBuilder) Build(w io.Writer) error {
	b.mu.Lock()
	defer b.mu.Unlock()

	// Create inner manifest
	inner := &MFFile{
		Version:   MFFile_VERSION_ONE,
		CreatedAt: newTimestampFromTime(b.createdAt),
		Files:     b.files,
	}

	// Create a temporary manifest to use existing serialization
	m := &manifest{
		pbInner: inner,
	}

	// Generate outer wrapper
	if err := m.generateOuter(); err != nil {
		return err
	}

	// Generate final output
	if err := m.generate(); err != nil {
		return err
	}

	// Write to output
	_, err := w.Write(m.output.Bytes())
	return err
}
