package repo

import (
	"errors"
	"log"

	"github.com/0x2e/fusion/model"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

var DB *gorm.DB

func Init(dbPath string) {
	conn, err := gorm.Open(
		sqlite.Open(dbPath),
		&gorm.Config{TranslateError: true},
	)
	if err != nil {
		panic(err)
	}
	DB = conn

	migrage()
	registerCallback()
}

func migrage() {
	// The verison after v0.8.7 will add a unique index to Feed.Link.
	// We must delete any duplicate feeds before AutoMigrate applies the
	// new unique constraint.
	err := DB.Transaction(func(tx *gorm.DB) error {
		// skip when it's the first launch
		if !tx.Migrator().HasTable(&model.Feed{}) || !tx.Migrator().HasTable(&model.Item{}) {
			return nil
		}

		// query duplicate feeds
		dupFeeds := make([]model.Feed, 0)
		err := tx.Model(&model.Feed{}).Where(
			"link IN (?)",
			tx.Model(&model.Feed{}).Select("link").Group("link").
				Having("count(link) > 1"),
		).Order("link, id").Find(&dupFeeds).Error
		if err != nil {
			return err
		}

		// filter out feeds that will be deleted.
		// we've queried with order, so the first one is the one we should keep.
		distinct := map[string]uint{}
		deleteIDs := make([]uint, 0, len(dupFeeds))
		for _, f := range dupFeeds {
			if _, ok := distinct[*f.Link]; !ok {
				distinct[*f.Link] = f.ID
				continue
			}
			deleteIDs = append(deleteIDs, f.ID)
			log.Println("delete duplicate feed: ", f.ID, *f.Name, *f.Link)
		}

		if len(deleteIDs) > 0 {
			// **hard** delete duplicate feeds and their items
			err = tx.Where("id IN ?", deleteIDs).Unscoped().Delete(&model.Feed{}).Error
			if err != nil {
				return err
			}
			return tx.Where("feed_id IN ?", deleteIDs).Unscoped().Delete(&model.Item{}).Error
		}
		return nil
	})
	if err != nil {
		panic(err)
	}

	// FIX: gorm not auto drop index and change 'not null'
	if err := DB.AutoMigrate(&model.Feed{}, &model.Group{}, &model.Item{}); err != nil {
		panic(err)
	}

	defaultGroup := "Default"
	if err := DB.Model(&model.Group{}).Where("id = ?", 1).
		FirstOrCreate(&model.Group{ID: 1, Name: &defaultGroup}).Error; err != nil {
		panic(err)
	}
}

func registerCallback() {
	if err := DB.Callback().Query().After("*").Register("convert_error", func(db *gorm.DB) {
		if errors.Is(db.Error, gorm.ErrRecordNotFound) {
			db.Error = ErrNotFound
		}
	}); err != nil {
		panic(err)
	}

	if err := DB.Callback().Create().After("*").Register("convert_error", func(db *gorm.DB) {
		if errors.Is(db.Error, gorm.ErrDuplicatedKey) {
			db.Error = ErrDuplicatedKey
		}
	}); err != nil {
		panic(err)
	}

	if err := DB.Callback().Update().After("*").Register("convert_error", func(db *gorm.DB) {
		if db.Error == nil && db.RowsAffected == 0 {
			db.Error = ErrNotFound
		}
		if errors.Is(db.Error, gorm.ErrDuplicatedKey) {
			db.Error = ErrDuplicatedKey
		}
	}); err != nil {
		panic(err)
	}

	if err := DB.Callback().Delete().After("*").Register("convert_error", func(db *gorm.DB) {
		if db.Error == nil && db.RowsAffected == 0 {
			db.Error = ErrNotFound
		}
		if errors.Is(db.Error, gorm.ErrDuplicatedKey) {
			db.Error = ErrDuplicatedKey
		}
	}); err != nil {
		panic(err)
	}
}
