package database

import (
	"errors"
	"os"
	"time"

	"git.ar21.de/yolokube/country-geo-locations/internal/cmd"
	"git.ar21.de/yolokube/country-geo-locations/pkg/geoloc"
	"github.com/hashicorp/go-memdb"
)

var (
	ErrUnknownInterface = errors.New("unknown interface structure")
	ErrIPNetNotFound    = errors.New("IP net not found")
)

type Database struct {
	ready bool

	config *cmd.AppSettings
	db     *memdb.MemDB
}

func NewDatabase(config *cmd.AppSettings) (*Database, error) {
	database, err := memdb.NewMemDB(
		&memdb.DBSchema{
			Tables: map[string]*memdb.TableSchema{
				"ipinfo": {
					Name: "ipinfo",
					Indexes: map[string]*memdb.IndexSchema{
						"id": {
							Name:    "id",
							Unique:  true,
							Indexer: &memdb.UintFieldIndex{Field: "IPNumFrom"},
						},
					},
				},
			},
		},
	)
	if err != nil {
		return nil, err
	}

	return &Database{
		ready:  false,
		config: config,
		db:     database,
	}, nil
}

func (d *Database) Load(ipinfos []geoloc.IPInfo) error {
	txn := d.db.Txn(true)
	defer txn.Abort()

	for _, ipinfo := range ipinfos {
		if err := txn.Insert("ipinfo", ipinfo); err != nil {
			return err
		}
	}

	txn.Commit()
	d.ready = true
	return nil
}

func (d *Database) IsReady() bool {
	return d.ready
}

func (d *Database) SearchIPNet(ipnetnum uint) (*geoloc.IPInfo, error) {
	txn := d.db.Txn(false)
	defer txn.Abort()

	var (
		ipinfo geoloc.IPInfo
		ok     bool
	)
	for {
		raw, err := txn.First("ipinfo", "id", ipnetnum)
		if err != nil {
			return nil, err
		}

		if raw != nil {
			ipinfo, ok = raw.(geoloc.IPInfo)
			if !ok {
				return nil, ErrUnknownInterface
			}
			break
		}

		if ipnetnum == 0 {
			return nil, ErrIPNetNotFound
		}

		ipnetnum -= geoloc.CalculationValue
	}

	return &ipinfo, nil
}

func (d *Database) Timestamp() (time.Time, error) {
	file, err := os.Open(d.config.DataFile)
	if err != nil {
		return time.Time{}, err
	}

	stats, err := file.Stat()
	if err != nil {
		return time.Time{}, err
	}

	return stats.ModTime(), nil
}