package entity

import (
	"fmt"
	"time"

	"github.com/jinzhu/gorm"

	"github.com/photoprism/photoprism/internal/migrate"
	"github.com/photoprism/photoprism/pkg/sanitize"
)

type Tables map[string]interface{}

// Entities contains database entities and their table names.
var Entities = Tables{
	migrate.Migration{}.TableName(): &migrate.Migration{},
	"errors":                        &Error{},
	"addresses":                     &Address{},
	"users":                         &User{},
	"accounts":                      &Account{},
	"folders":                       &Folder{},
	"duplicates":                    &Duplicate{},
	File{}.TableName():              &File{},
	"files_share":                   &FileShare{},
	"files_sync":                    &FileSync{},
	Photo{}.TableName():             &Photo{},
	"details":                       &Details{},
	Place{}.TableName():             &Place{},
	Cell{}.TableName():              &Cell{},
	"cameras":                       &Camera{},
	"lenses":                        &Lens{},
	"countries":                     &Country{},
	"albums":                        &Album{},
	"photos_albums":                 &PhotoAlbum{},
	"labels":                        &Label{},
	"categories":                    &Category{},
	"photos_labels":                 &PhotoLabel{},
	"keywords":                      &Keyword{},
	"photos_keywords":               &PhotoKeyword{},
	"passwords":                     &Password{},
	"links":                         &Link{},
	Subject{}.TableName():           &Subject{},
	Face{}.TableName():              &Face{},
	Marker{}.TableName():            &Marker{},
}

// WaitForMigration waits for the database migration to be successful.
func (list Tables) WaitForMigration(db *gorm.DB) {
	type RowCount struct {
		Count int
	}

	attempts := 100
	for name := range list {
		for i := 0; i <= attempts; i++ {
			count := RowCount{}
			if err := db.Raw(fmt.Sprintf("SELECT COUNT(*) AS count FROM %s", name)).Scan(&count).Error; err == nil {
				log.Tracef("entity: %s migrated", sanitize.Log(name))
				break
			} else {
				log.Debugf("entity: waiting for %s migration (%s)", sanitize.Log(name), err.Error())
			}

			if i == attempts {
				panic("migration failed")
			}

			time.Sleep(50 * time.Millisecond)
		}
	}
}

// Truncate removes all data from tables without dropping them.
func (list Tables) Truncate(db *gorm.DB) {
	for name := range list {
		if err := db.Exec(fmt.Sprintf("DELETE FROM %s WHERE 1", name)).Error; err == nil {
			// log.Debugf("entity: removed all data from %s", name)
			break
		} else if err.Error() != "record not found" {
			log.Debugf("entity: %s in %s", err, sanitize.Log(name))
		}
	}
}

// Migrate migrates all database tables of registered entities.
func (list Tables) Migrate(db *gorm.DB, runFailed bool) {
	for name, entity := range list {
		if err := db.AutoMigrate(entity).Error; err != nil {
			log.Debugf("entity: %s (waiting 1s)", err.Error())

			time.Sleep(time.Second)

			if err := db.AutoMigrate(entity).Error; err != nil {
				log.Errorf("entity: failed migrating %s", sanitize.Log(name))
				panic(err)
			}
		}
	}

	if err := migrate.Auto(db, runFailed); err != nil {
		log.Error(err)
	}
}

// Drop drops all database tables of registered entities.
func (list Tables) Drop(db *gorm.DB) {
	for _, entity := range list {
		if err := db.DropTableIfExists(entity).Error; err != nil {
			panic(err)
		}
	}
}
