package wal

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

// SegmentInfo holds metadata about a WAL segment file.
type SegmentInfo struct {
	FilePath      string
	SegmentID     uint64
	StartSequence uint64
}

// ParseSegmentFilename extracts the segment ID from a filename matching the
// pattern "segment-{N}.wal". Returns (N, true) on match, (0, false) otherwise.
func ParseSegmentFilename(name string) (segmentID uint64, ok bool) {
	if !strings.HasPrefix(name, "segment-") || !strings.HasSuffix(name, ".wal") {
		return 0, false
	}
	// Strip "segment-" prefix and ".wal" suffix.
	middle := name[len("segment-") : len(name)-len(".wal")]
	if len(middle) == 0 {
		return 0, false
	}
	id, err := strconv.ParseUint(middle, 10, 64)
	if err != nil {
		return 0, false
	}
	return id, true
}

var (
	// ErrSegmentGap indicates missing WAL segment(s) in the expected sequence.
	ErrSegmentGap = errors.New("wal: gap detected in segment sequence")
)

// ScanSegments discovers WAL segment files in dir, filters those with
// SegmentID >= recoverySegmentID, validates file headers, and returns them
// sorted by SegmentID ascending. Returns an error if a gap is detected in the
// segment ID sequence.
func ScanSegments(dir string, recoverySegmentID uint64) ([]*SegmentInfo, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("wal: scan segments: %w", err)
	}

	var candidates []*SegmentInfo
	for _, ent := range entries {
		if ent.IsDir() {
			continue
		}
		segID, ok := ParseSegmentFilename(ent.Name())
		if !ok {
			continue
		}
		if segID < recoverySegmentID {
			continue
		}
		candidates = append(candidates, &SegmentInfo{
			FilePath:  filepath.Join(dir, ent.Name()),
			SegmentID: segID,
		})
	}

	if len(candidates) == 0 {
		return nil, nil
	}

	// Sort by SegmentID ascending.
	sort.Slice(candidates, func(i, j int) bool {
		return candidates[i].SegmentID < candidates[j].SegmentID
	})

	// Decode each file header to populate StartSequence and validate.
	for _, si := range candidates {
		hdr, err := readFileHeader(si.FilePath)
		if err != nil {
			return nil, fmt.Errorf("wal: segment %d: %w", si.SegmentID, err)
		}
		si.StartSequence = hdr.StartSequence
	}

	// Verify continuity: segment IDs must form a consecutive sequence
	// starting from recoverySegmentID.
	for i, si := range candidates {
		expected := recoverySegmentID + uint64(i)
		if si.SegmentID != expected {
			return nil, fmt.Errorf("%w: expected segment %d, found %d",
				ErrSegmentGap, expected, si.SegmentID)
		}
	}

	return candidates, nil
}

// readFileHeader opens the file, reads the header portion, and decodes it.
func readFileHeader(path string) (*WalFileHeader, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	hdrBuf := make([]byte, WalFileHeaderSize)
	n, err := f.Read(hdrBuf)
	if err != nil {
		return nil, fmt.Errorf("read header: %w", err)
	}
	if n < WalFileHeaderSize {
		return nil, fmt.Errorf("read header: got %d bytes, need %d", n, WalFileHeaderSize)
	}
	return DecodeWalHeader(hdrBuf)
}
