package database

import (
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"os"
	"path/filepath"

	pebbledriver "git.sr.ht/~ashkeel/kilovolt-driver-pebble"
	kv "git.sr.ht/~ashkeel/kilovolt/v12"
	"git.sr.ht/~ashkeel/strimertul/utils"
	"github.com/cockroachdb/pebble"
)

type PebbleDatabase struct {
	db     *pebble.DB
	hub    *kv.Hub
	logger *slog.Logger
}

// NewPebble creates a new database driver instance with an underlying Pebble database
func NewPebble(directory string) (*PebbleDatabase, error) {
	db, err := pebble.Open(directory, &pebble.Options{})
	if err != nil {
		return nil, fmt.Errorf("could not open DB: %w", err)
	}

	// Create file for autodetect
	err = os.WriteFile(filepath.Join(directory, "stul-driver"), []byte("pebble"), 0o644)
	if err != nil {
		return nil, fmt.Errorf("could not write driver file: %w", err)
	}

	p := &PebbleDatabase{
		db:  db,
		hub: nil,
	}

	return p, nil
}

func (p *PebbleDatabase) Hub() *kv.Hub {
	if p.hub == nil {
		p.hub, _ = kv.NewHub(pebbledriver.NewPebbleBackend(p.db, true), kv.HubOptions{
			Logger: p.logger,
		})
	}
	return p.hub
}

func (p *PebbleDatabase) Close() error {
	if p.hub != nil {
		p.hub.Close()
	}
	err := p.db.Close()
	if err != nil {
		return fmt.Errorf("could not close database: %w", err)
	}
	return nil
}

func (p *PebbleDatabase) Import(entries map[string]string) error {
	batch := p.db.NewBatch()
	for key, value := range entries {
		err := batch.Set([]byte(key), []byte(value), &pebble.WriteOptions{})
		if err != nil {
			return err
		}
	}
	return batch.Commit(&pebble.WriteOptions{})
}

func (p *PebbleDatabase) Export(file io.Writer) error {
	return p.Backup(file)
}

func (p *PebbleDatabase) Restore(file io.Reader) error {
	in := make(map[string]string)
	err := json.NewDecoder(file).Decode(&in)
	if err != nil {
		return fmt.Errorf("could not decode backup: %w", err)
	}

	b := p.db.NewBatch()
	for k, v := range in {
		err = b.Set([]byte(k), []byte(v), nil)
		if err != nil {
			return fmt.Errorf("could not set key %s: %w", k, err)
		}
	}

	return b.Commit(&pebble.WriteOptions{Sync: true})
}

func (p *PebbleDatabase) Backup(file io.Writer) error {
	snapshot := p.db.NewSnapshot()
	defer utils.Close(snapshot)

	iter, err := snapshot.NewIter(&pebble.IterOptions{})
	if err != nil {
		return err
	}
	defer utils.Close(iter)

	out := make(map[string]string)
	for iter.First(); iter.Valid(); iter.Next() {
		val, err := iter.ValueAndErr()
		if err != nil {
			return err
		}
		out[string(iter.Key())] = string(val)
	}
	return json.NewEncoder(file).Encode(out)
}
