package recovery

import (
	"errors"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"gordenko.dev/dima/qb"
	"gordenko.dev/dima/qb/freelist"
	"gordenko.dev/dima/qb/storage"
)

// Result зберігає результати для обох типів файлів
type FileVersion struct {
	Name    string
	Version int
}

// FindMaxSnapshotFiles шукає файли з розширеннями .snapshot_XXX та .wal_XXX
// і повертає структуру з максимальними номерами.
func findFileLatestVersion(dir string, extPrefix string) (FileVersion, error) {
	var x FileVersion
	if !strings.HasPrefix(extPrefix, ".") {
		extPrefix = "." + extPrefix
	}
	entries, err := os.ReadDir(dir)
	if err != nil {
		return x, err
	}
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		ext := filepath.Ext(name)

		if strings.HasPrefix(ext, extPrefix) {
			version, err := strconv.Atoi(strings.TrimPrefix(ext, extPrefix))
			if err == nil {
				if version > x.Version || x.Name == "" {
					x.Name = name
					x.Version = version
				}
			}
		}
	}
	return x, nil
}

var (
	ErrLostSnapshot = errors.New("lost snapshot")
)

type RecoveryFiles struct {
	Snapshot       string
	WAL            string
	SnapshotNumber int
}

func resolveRecoveryFiles(dir string) (_ RecoveryFiles, err error) {
	wal, err := findFileLatestVersion(dir, "wal_")
	if err != nil {
		return
	}
	snapshot, err := findFileLatestVersion(dir, "snapshot_")
	if err != nil {
		return
	}
	if snapshot.Name != "" {
		if wal.Name != "" {
			if snapshot.Version == wal.Version {
				return RecoveryFiles{
					Snapshot:       snapshot.Name,
					WAL:            wal.Name,
					SnapshotNumber: snapshot.Version,
				}, nil
			} else if snapshot.Version > wal.Version {
				return RecoveryFiles{
					Snapshot:       snapshot.Name,
					SnapshotNumber: snapshot.Version,
				}, nil
			} else {
				// WAL version > snapshot version
				err = ErrLostSnapshot
				return
			}
		} else {
			return RecoveryFiles{
				Snapshot:       snapshot.Name,
				SnapshotNumber: snapshot.Version,
			}, nil
		}
	} else {
		if wal.Name != "" {
			if wal.Version == 0 {
				return RecoveryFiles{
					WAL: wal.Name,
				}, nil
			} else {
				err = ErrLostSnapshot
				return
			}
		} else {
			// no files - ok, 1st start of database
			return
		}
	}
}

type RecoveryReport struct {
	//Metrics []
	SnapshotNumber int
	WAL            string
	IndexFreeList  *freelist.FreeList
	DataFreeList   *freelist.FreeList
}

func Recovery(dir string, databaseName string) (_ RecoveryReport, err error) {
	files, err := resolveRecoveryFiles(dir)
	if err != nil {
		return
	}
	var (
		freeIndexPages []uint32
		freeDataPages  []uint32
	)
	if files.Snapshot != "" {
		var out storage.ReadSnapshotOut
		out, err = storage.ReadSnapshot(files.Snapshot)
		if err != nil {
			return
		}
		//
		freeIndexPages = out.IndexPageNumbers
		freeDataPages = out.DataPageNumbers
	}

	if files.WAL != "" {
		var (
			walReader   *storage.WALReader
			walReplayer *storage.WALReplayer
		)
		walReader, err = storage.NewWALReader(storage.WALReaderOptions{
			//FileName:   JoinWALFileName(s.dir, files.WAL),
			BufferSize: 8 * 1024 * 1024,
		})
		if err != nil {
			return
		}

		walReplayer, err = storage.NewWALReplayer(storage.WALReplayerOptions{
			WALReader:      walReader,
			Metrics:        snapshot.Metrics,
			FreeIndexPages: freeIndexPages,
			FreeDataPages:  freeDataPages,
		})
		if err != nil {
			return
		}

		err = walReplayer.Replay()
		if err != nil {
			return
		}
		// перезаписую сторінки із останнього комміта
		var dataFile *os.File
		dataFile, err = os.OpenFile(qb.GetDataFilePath(dir, databaseName), os.O_WRONLY, 0666)
		if err != nil {
			return
		}
		err = storage.WriteDataPages(dataFile, walReplayer.DataPages())
		if err != nil {
			return
		}
		err = dataFile.Close()
		if err != nil {
			return
		}
		var indexFile *os.File
		indexFile, err = os.OpenFile(qb.GetIndexFilePath(dir, databaseName), os.O_WRONLY, 0666)
		if err != nil {
			return
		}
		err = storage.WriteIndexPages(indexFile, walReplayer.IndexPages())
		if err != nil {
			return
		}
		err = indexFile.Close()
		if err != nil {
			return
		}
	}

	indexFreeList, err := freelist.New(freelist.Options{
		PageSize:      2048,
		BaseFilePath:  filepath.Join(dir, databaseName+".free_index"),
		DeltaFilePath: filepath.Join(dir, databaseName+".free_index_delta"),
	})

	// FIX free pages sync
	dataFreeList, err := freelist.New(freelist.Options{
		PageSize:      2048,
		BaseFilePath:  filepath.Join(dir, databaseName+".free_data"),
		DeltaFilePath: filepath.Join(dir, databaseName+".free_data_delta"),
	})

	return RecoveryReport{
		SnapshotNumber: files.SnapshotNumber,
		WAL:            files.WAL,
		IndexFreeList:  indexFreeList,
		DataFreeList:   dataFreeList,
	}, nil
}

//func (s *Database) relayMetricsToMetrics(replayMetrics map[uint32]*ReplayMetric) {
//for metricID, x := range replayMetrics {
//s.metrics[metricID] = x.ToMetric() FIX
//}
//}
