package storage

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	bolt "go.etcd.io/bbolt"
)

var (
	bucketAppConfig  = []byte("AppConfig")
	bucketCategories = []byte("Categories")
	bucketEntries    = []byte("Entries")
	bucketAdmins     = []byte("Admins")

	keyTocMsgID = []byte("toc_msg_id")
)

type Category struct {
	Name      string    `json:"name"`
	Order     int       `json:"order"`
	CreatedAt time.Time `json:"created_at"`
}

type Entry struct {
	ID          string    `json:"id"`
	Category    string    `json:"category"`
	Title       string    `json:"title"`
	Link        string    `json:"link,omitempty"` // 兼容旧数据
	ChannelLink string    `json:"channel_link,omitempty"`
	DirectLink  string    `json:"direct_link,omitempty"`
	Timestamp   time.Time `json:"timestamp"`
}

func (e Entry) MessageLink() string {
	if e.ChannelLink != "" {
		return e.ChannelLink
	}
	return e.Link
}

func (e Entry) HasDirectLink() bool {
	direct := strings.TrimSpace(e.DirectLink)
	return direct != "" && direct != e.MessageLink()
}

type Storage struct {
	db *bolt.DB
}

func New(path string) (*Storage, error) {
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create data directory: %w", err)
	}

	db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return nil, fmt.Errorf("failed to open database: %w", err)
	}

	s := &Storage{db: db}
	if err := s.initBuckets(); err != nil {
		db.Close()
		return nil, err
	}

	return s, nil
}

func (s *Storage) initBuckets() error {
	return s.db.Update(func(tx *bolt.Tx) error {
		buckets := [][]byte{bucketAppConfig, bucketCategories, bucketEntries, bucketAdmins}
		for _, b := range buckets {
			if _, err := tx.CreateBucketIfNotExists(b); err != nil {
				return fmt.Errorf("failed to create bucket %s: %w", b, err)
			}
		}
		return nil
	})
}

func (s *Storage) Close() error {
	return s.db.Close()
}

func (s *Storage) GetTocMsgID() (int, error) {
	var msgID int
	err := s.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketAppConfig)
		v := b.Get(keyTocMsgID)
		if v != nil && len(v) >= 4 {
			msgID = int(binary.BigEndian.Uint32(v))
		}
		return nil
	})
	return msgID, err
}

func (s *Storage) SetTocMsgID(msgID int) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketAppConfig)
		buf := make([]byte, 4)
		binary.BigEndian.PutUint32(buf, uint32(msgID))
		return b.Put(keyTocMsgID, buf)
	})
}

func encodeJSON(v any) ([]byte, error) {
	return json.Marshal(v)
}

func decodeJSON(data []byte, v any) error {
	return json.Unmarshal(data, v)
}

// Admin management

func (s *Storage) AddAdmin(userID int64) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketAdmins)
		key := make([]byte, 8)
		binary.BigEndian.PutUint64(key, uint64(userID))
		return b.Put(key, []byte("1"))
	})
}

func (s *Storage) RemoveAdmin(userID int64) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketAdmins)
		key := make([]byte, 8)
		binary.BigEndian.PutUint64(key, uint64(userID))
		return b.Delete(key)
	})
}

func (s *Storage) IsAdmin(userID int64) bool {
	var exists bool
	s.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketAdmins)
		key := make([]byte, 8)
		binary.BigEndian.PutUint64(key, uint64(userID))
		exists = b.Get(key) != nil
		return nil
	})
	return exists
}

func (s *Storage) ListAdmins() ([]int64, error) {
	var admins []int64
	err := s.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(bucketAdmins)
		return b.ForEach(func(k, v []byte) error {
			if len(k) == 8 {
				userID := int64(binary.BigEndian.Uint64(k))
				admins = append(admins, userID)
			}
			return nil
		})
	})
	return admins, err
}
