package migrations

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/jmoiron/sqlx"
	"github.com/stashapp/stash/internal/manager/config"
	"github.com/stashapp/stash/pkg/logger"
	"github.com/stashapp/stash/pkg/sqlite"
)

type schema60Migrator struct {
	migrator
}

func post60(ctx context.Context, db *sqlx.DB) error {
	logger.Info("Running post-migration for schema version 60")

	m := schema60Migrator{
		migrator: migrator{
			db: db,
		},
	}

	return m.migrate(ctx)
}

func (m *schema60Migrator) decodeJSON(s string, v interface{}) {
	if s == "" {
		return
	}

	if err := json.Unmarshal([]byte(s), v); err != nil {
		logger.Errorf("error decoding json %q: %v", s, err)
	}
}

type schema60DefaultFilters map[string]interface{}

func (m *schema60Migrator) migrate(ctx context.Context) error {

	// save default filters into the UI config
	if err := m.withTxn(ctx, func(tx *sqlx.Tx) error {
		query := "SELECT id, mode, find_filter, object_filter, ui_options FROM `saved_filters` WHERE `name` = ''"

		rows, err := tx.Query(query)
		if err != nil {
			return err
		}
		defer rows.Close()

		defaultFilters := make(schema60DefaultFilters)

		for rows.Next() {
			var (
				id              int
				mode            string
				findFilterStr   string
				objectFilterStr string
				uiOptionsStr    string
			)

			if err := rows.Scan(&id, &mode, &findFilterStr, &objectFilterStr, &uiOptionsStr); err != nil {
				return err
			}

			// convert the filters to the correct format
			findFilter := make(map[string]interface{})
			objectFilter := make(map[string]interface{})
			uiOptions := make(map[string]interface{})

			m.decodeJSON(findFilterStr, &findFilter)
			m.decodeJSON(objectFilterStr, &objectFilter)
			m.decodeJSON(uiOptionsStr, &uiOptions)

			o := map[string]interface{}{
				"mode":          mode,
				"find_filter":   findFilter,
				"object_filter": objectFilter,
				"ui_options":    uiOptions,
			}

			defaultFilters[strings.ToLower(mode)] = o
		}

		if err := rows.Err(); err != nil {
			return err
		}

		if err := m.saveDefaultFilters(defaultFilters); err != nil {
			return fmt.Errorf("saving default filters: %w", err)
		}

		// remove the default filters from the database
		query = "DELETE FROM `saved_filters` WHERE `name` = ''"
		if _, err := tx.Exec(query); err != nil {
			return fmt.Errorf("deleting default filters: %w", err)
		}

		return nil
	}); err != nil {
		return err
	}

	return nil
}

func (m *schema60Migrator) saveDefaultFilters(defaultFilters schema60DefaultFilters) error {
	if len(defaultFilters) == 0 {
		logger.Debugf("no default filters to save")
		return nil
	}

	// save the default filters into the UI config
	config := config.GetInstance()

	orgPath := config.GetConfigFile()

	if orgPath == "" {
		// no config file to migrate (usually in a test or new system)
		logger.Debugf("no config file to migrate")
		return nil
	}

	uiConfig := config.GetUIConfiguration()
	if uiConfig == nil {
		uiConfig = make(map[string]interface{})
	}

	// if the defaultFilters key already exists, don't overwrite them
	if _, found := uiConfig["defaultFilters"]; found {
		logger.Warn("defaultFilters already exists in the UI config, skipping migration")
		return nil
	}

	if err := m.backupConfig(orgPath); err != nil {
		return fmt.Errorf("backing up config: %w", err)
	}

	uiConfig["defaultFilters"] = map[string]interface{}(defaultFilters)
	config.SetUIConfiguration(uiConfig)

	if err := config.Write(); err != nil {
		return fmt.Errorf("failed to write config: %w", err)
	}

	return nil
}

func (m *schema60Migrator) backupConfig(orgPath string) error {
	c := config.GetInstance()

	// save a backup of the original config file
	backupPath := fmt.Sprintf("%s.59.%s", orgPath, time.Now().Format("20060102_150405"))

	data, err := c.Marshal()
	if err != nil {
		return fmt.Errorf("failed to marshal backup config: %w", err)
	}

	logger.Infof("Backing up config to %s", backupPath)
	if err := os.WriteFile(backupPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write backup config: %w", err)
	}

	return nil
}

func init() {
	sqlite.RegisterPostMigration(60, post60)
}
