package cmd

import (
	"fmt"
	"os"
	"os/signal"

	"github.com/spf13/afero"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/supabase/cli/internal/migration/fetch"
	"github.com/supabase/cli/internal/migration/list"
	"github.com/supabase/cli/internal/migration/new"
	"github.com/supabase/cli/internal/migration/repair"
	"github.com/supabase/cli/internal/migration/squash"
	"github.com/supabase/cli/internal/migration/up"
	"github.com/supabase/cli/internal/utils"
	"github.com/supabase/cli/internal/utils/flags"
)

var (
	migrationCmd = &cobra.Command{
		GroupID: groupLocalDev,
		Use:     "migration",
		Aliases: []string{"migrations"},
		Short:   "Manage database migration scripts",
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
			cmd.SetContext(ctx)
			return cmd.Root().PersistentPreRunE(cmd, args)
		},
	}

	migrationListCmd = &cobra.Command{
		Use:   "list",
		Short: "List local and remote migrations",
		RunE: func(cmd *cobra.Command, args []string) error {
			return list.Run(cmd.Context(), flags.DbConfig, afero.NewOsFs())
		},
	}

	migrationNewCmd = &cobra.Command{
		Use:   "new <migration name>",
		Short: "Create an empty migration script",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return new.Run(args[0], os.Stdin, afero.NewOsFs())
		},
	}

	targetStatus = utils.EnumFlag{
		Allowed: []string{
			repair.Applied,
			repair.Reverted,
		},
	}

	migrationRepairCmd = &cobra.Command{
		Use:   "repair [version] ...",
		Short: "Repair the migration history table",
		RunE: func(cmd *cobra.Command, args []string) error {
			return repair.Run(cmd.Context(), flags.DbConfig, args, targetStatus.Value, afero.NewOsFs())
		},
		PostRun: func(cmd *cobra.Command, args []string) {
			fmt.Println("Finished " + utils.Aqua("supabase migration repair") + ".")
		},
	}

	migrationVersion string

	migrationSquashCmd = &cobra.Command{
		Use:   "squash",
		Short: "Squash migrations to a single file",
		RunE: func(cmd *cobra.Command, args []string) error {
			return squash.Run(cmd.Context(), migrationVersion, flags.DbConfig, afero.NewOsFs())
		},
		PostRun: func(cmd *cobra.Command, args []string) {
			fmt.Println("Finished " + utils.Aqua("supabase migration squash") + ".")
		},
	}

	migrationUpCmd = &cobra.Command{
		Use:   "up",
		Short: "Apply pending migrations to local database",
		RunE: func(cmd *cobra.Command, args []string) error {
			return up.Run(cmd.Context(), includeAll, flags.DbConfig, afero.NewOsFs())
		},
		PostRun: func(cmd *cobra.Command, args []string) {
			fmt.Println("Local database is up to date.")
		},
	}

	migrationFetchCmd = &cobra.Command{
		Use:   "fetch",
		Short: "Fetch migration files from history table",
		RunE: func(cmd *cobra.Command, args []string) error {
			return fetch.Run(cmd.Context(), flags.DbConfig, afero.NewOsFs())
		},
	}
)

func init() {
	// Build list command
	listFlags := migrationListCmd.Flags()
	listFlags.String("db-url", "", "Lists migrations of the database specified by the connection string (must be percent-encoded).")
	listFlags.Bool("linked", true, "Lists migrations applied to the linked project.")
	listFlags.Bool("local", false, "Lists migrations applied to the local database.")
	migrationListCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
	listFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
	cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", listFlags.Lookup("password")))
	migrationListCmd.MarkFlagsMutuallyExclusive("db-url", "password")
	migrationCmd.AddCommand(migrationListCmd)
	// Build repair command
	repairFlags := migrationRepairCmd.Flags()
	repairFlags.Var(&targetStatus, "status", "Version status to update.")
	cobra.CheckErr(migrationRepairCmd.MarkFlagRequired("status"))
	repairFlags.String("db-url", "", "Repairs migrations of the database specified by the connection string (must be percent-encoded).")
	repairFlags.Bool("linked", true, "Repairs the migration history of the linked project.")
	repairFlags.Bool("local", false, "Repairs the migration history of the local database.")
	migrationRepairCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
	repairFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
	cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", repairFlags.Lookup("password")))
	migrationRepairCmd.MarkFlagsMutuallyExclusive("db-url", "password")
	migrationCmd.AddCommand(migrationRepairCmd)
	// Build squash command
	squashFlags := migrationSquashCmd.Flags()
	squashFlags.StringVar(&migrationVersion, "version", "", "Squash up to the specified version.")
	squashFlags.String("db-url", "", "Squashes migrations of the database specified by the connection string (must be percent-encoded).")
	squashFlags.Bool("linked", false, "Squashes the migration history of the linked project.")
	squashFlags.Bool("local", true, "Squashes the migration history of the local database.")
	migrationSquashCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
	squashFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
	cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", squashFlags.Lookup("password")))
	migrationSquashCmd.MarkFlagsMutuallyExclusive("db-url", "password")
	migrationCmd.AddCommand(migrationSquashCmd)
	// Build up command
	upFlags := migrationUpCmd.Flags()
	upFlags.BoolVar(&includeAll, "include-all", false, "Include all migrations not found on remote history table.")
	upFlags.String("db-url", "", "Applies migrations to the database specified by the connection string (must be percent-encoded).")
	upFlags.Bool("linked", false, "Applies pending migrations to the linked project.")
	upFlags.Bool("local", true, "Applies pending migrations to the local database.")
	migrationUpCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
	migrationCmd.AddCommand(migrationUpCmd)
	// Build up command
	fetchFlags := migrationFetchCmd.Flags()
	fetchFlags.String("db-url", "", "Fetches migrations from the database specified by the connection string (must be percent-encoded).")
	fetchFlags.Bool("linked", true, "Fetches migration history from the linked project.")
	fetchFlags.Bool("local", false, "Fetches migration history from the local database.")
	migrationFetchCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
	migrationCmd.AddCommand(migrationFetchCmd)
	// Build new command
	migrationCmd.AddCommand(migrationNewCmd)
	rootCmd.AddCommand(migrationCmd)
}
