package store

import (
	"encoding/json"
	"fmt"
	"sort"

	bolt "go.etcd.io/bbolt"
)

var (
	bucketProfiles = []byte("profiles")
	bucketRankings = []byte("rankings")
)

type DB struct {
	db *bolt.DB
}

type RunRecord struct {
	Player string `json:"player"`
	Floor  int    `json:"floor"`
	Score  int    `json:"score"`
}

func Open(path string) (*DB, error) {
	db, err := bolt.Open(path, 0600, nil)
	if err != nil {
		return nil, err
	}
	err = db.Update(func(tx *bolt.Tx) error {
		if _, err := tx.CreateBucketIfNotExists(bucketProfiles); err != nil {
			return err
		}
		if _, err := tx.CreateBucketIfNotExists(bucketRankings); err != nil {
			return err
		}
		return nil
	})
	return &DB{db: db}, err
}

func (d *DB) Close() error {
	return d.db.Close()
}

func (d *DB) SaveProfile(fingerprint, name string) error {
	return d.db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket(bucketProfiles).Put([]byte(fingerprint), []byte(name))
	})
}

func (d *DB) GetProfile(fingerprint string) (string, error) {
	var name string
	err := d.db.View(func(tx *bolt.Tx) error {
		v := tx.Bucket(bucketProfiles).Get([]byte(fingerprint))
		if v == nil {
			return fmt.Errorf("profile not found")
		}
		name = string(v)
		return nil
	})
	return name, err
}

func (d *DB) SaveRun(player string, floor, score int) error {
	return d.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketRankings)
		id, _ := b.NextSequence()
		record := RunRecord{Player: player, Floor: floor, Score: score}
		data, err := json.Marshal(record)
		if err != nil {
			return err
		}
		return b.Put([]byte(fmt.Sprintf("%010d", id)), data)
	})
}

func (d *DB) TopRuns(limit int) ([]RunRecord, error) {
	var runs []RunRecord
	err := d.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketRankings)
		return b.ForEach(func(k, v []byte) error {
			var r RunRecord
			if err := json.Unmarshal(v, &r); err != nil {
				return err
			}
			runs = append(runs, r)
			return nil
		})
	})
	if err != nil {
		return nil, err
	}
	sort.Slice(runs, func(i, j int) bool {
		if runs[i].Floor != runs[j].Floor {
			return runs[i].Floor > runs[j].Floor
		}
		return runs[i].Score > runs[j].Score
	})
	if len(runs) > limit {
		runs = runs[:limit]
	}
	return runs, nil
}
