package backend

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"sort"
	"time"

	bolt "go.etcd.io/bbolt"
)

type HistoryItem struct {
	ID          string `json:"id"`
	SpotifyID   string `json:"spotify_id"`
	Title       string `json:"title"`
	Artists     string `json:"artists"`
	Album       string `json:"album"`
	DurationStr string `json:"duration_str"`
	CoverURL    string `json:"cover_url"`
	Quality     string `json:"quality"`
	Format      string `json:"format"`
	Path        string `json:"path"`
	Source      string `json:"source"`
	Timestamp   int64  `json:"timestamp"`
}

var historyDB *bolt.DB

const (
	historyBucket = "DownloadHistory"
	maxHistory    = 10000
)

func InitHistoryDB(appName string) error {

	appDir, err := EnsureAppDir()
	if err != nil {
		return err
	}
	dbPath := filepath.Join(appDir, "history.db")

	db, err := bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return err
	}

	err = db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(historyBucket))
		return err
	})

	if err != nil {
		db.Close()
		return err
	}

	historyDB = db
	return nil
}

func CloseHistoryDB() {
	if historyDB != nil {
		historyDB.Close()
	}
}

func AddHistoryItem(item HistoryItem, appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte(historyBucket))
		if err != nil {
			return err
		}
		id, _ := b.NextSequence()

		item.ID = fmt.Sprintf("%d-%d", time.Now().UnixNano(), id)
		item.Timestamp = time.Now().Unix()

		buf, err := json.Marshal(item)
		if err != nil {
			return err
		}

		if b.Stats().KeyN >= maxHistory {
			c := b.Cursor()

			toDelete := maxHistory / 20
			if toDelete < 1 {
				toDelete = 1
			}

			count := 0
			for k, _ := c.First(); k != nil && count < toDelete; k, _ = c.Next() {
				if err := b.Delete(k); err != nil {
					return err
				}
				count++
			}
		}

		return b.Put([]byte(item.ID), buf)
	})
}

func GetHistoryItems(appName string) ([]HistoryItem, error) {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return nil, err
		}
	}
	var items []HistoryItem
	err := historyDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(historyBucket))
		if b == nil {
			return nil
		}
		c := b.Cursor()

		for k, v := c.First(); k != nil; k, v = c.Next() {
			var item HistoryItem
			if err := json.Unmarshal(v, &item); err == nil {
				items = append(items, item)
			}
		}
		return nil
	})

	sort.Slice(items, func(i, j int) bool {
		return items[i].Timestamp > items[j].Timestamp
	})

	return items, err
}

func ClearHistory(appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		return tx.DeleteBucket([]byte(historyBucket))
	})
}

type FetchHistoryItem struct {
	ID        string `json:"id"`
	URL       string `json:"url"`
	Type      string `json:"type"`
	Name      string `json:"name"`
	Info      string `json:"info"`
	Image     string `json:"image"`
	Data      string `json:"data"`
	Timestamp int64  `json:"timestamp"`
}

const (
	fetchHistoryBucket = "FetchHistory"
)

func AddFetchHistoryItem(item FetchHistoryItem, appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte(fetchHistoryBucket))
		if err != nil {
			return err
		}
		id, _ := b.NextSequence()

		if item.URL != "" {
			c := b.Cursor()
			for k, v := c.First(); k != nil; k, v = c.Next() {
				var existing FetchHistoryItem
				if err := json.Unmarshal(v, &existing); err == nil {
					if existing.URL == item.URL && existing.Type == item.Type {
						if err := b.Delete(k); err != nil {
							return err
						}
					}
				}
			}
		}

		item.ID = fmt.Sprintf("%d-%d", time.Now().UnixNano(), id)
		item.Timestamp = time.Now().Unix()

		buf, err := json.Marshal(item)
		if err != nil {
			return err
		}

		if b.Stats().KeyN >= maxHistory {
			c := b.Cursor()
			toDelete := maxHistory / 20
			if toDelete < 1 {
				toDelete = 1
			}
			count := 0
			for k, _ := c.First(); k != nil && count < toDelete; k, _ = c.Next() {
				if err := b.Delete(k); err != nil {
					return err
				}
				count++
			}
		}

		return b.Put([]byte(item.ID), buf)
	})
}

func GetFetchHistoryItems(appName string) ([]FetchHistoryItem, error) {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return nil, err
		}
	}
	var items []FetchHistoryItem
	err := historyDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(fetchHistoryBucket))
		if b == nil {
			return nil
		}
		c := b.Cursor()

		for k, v := c.First(); k != nil; k, v = c.Next() {
			var item FetchHistoryItem
			if err := json.Unmarshal(v, &item); err == nil {
				items = append(items, item)
			}
		}
		return nil
	})

	sort.Slice(items, func(i, j int) bool {
		return items[i].Timestamp > items[j].Timestamp
	})

	return items, err
}

func ClearFetchHistory(appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		return tx.DeleteBucket([]byte(fetchHistoryBucket))
	})
}

func ClearFetchHistoryByType(itemType string, appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(fetchHistoryBucket))
		if b == nil {
			return nil
		}

		var keysToDelete [][]byte

		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var item FetchHistoryItem
			if err := json.Unmarshal(v, &item); err == nil {
				if item.Type == itemType {
					keysToDelete = append(keysToDelete, k)
				}
			}
		}

		for _, k := range keysToDelete {
			if err := b.Delete(k); err != nil {
				return err
			}
		}
		return nil
	})
}

func DeleteHistoryItem(id string, appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(historyBucket))
		if b == nil {
			return nil
		}

		return b.Delete([]byte(id))
	})
}

func DeleteFetchHistoryItem(id string, appName string) error {
	if historyDB == nil {
		if err := InitHistoryDB(appName); err != nil {
			return err
		}
	}
	return historyDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(fetchHistoryBucket))
		if b == nil {
			return nil
		}
		return b.Delete([]byte(id))
	})
}
