package main

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"slices"
	"time"

	"github.com/urfave/cli/v3"

	"github.com/SecurityBrewery/catalyst/app"
	"github.com/SecurityBrewery/catalyst/app/data"
	"github.com/SecurityBrewery/catalyst/app/database/sqlc"
	"github.com/SecurityBrewery/catalyst/app/settings"
)

func main() {
	cmd := &cli.Command{
		Name:  "catalyst",
		Usage: "Catalyst CLI",
		Flags: []cli.Flag{
			&cli.StringFlag{Name: "app-url"},
			&cli.StringSliceFlag{Name: "flags"},
		},
		Commands: []*cli.Command{
			{
				Name: "migrate",
				Action: func(ctx context.Context, command *cli.Command) error {
					_, cleanup, err := setup(ctx, command)
					if err != nil {
						return fmt.Errorf("failed to setup catalyst: %w", err)
					}

					defer cleanup()

					return nil
				},
			},
			{
				Name:  "serve",
				Usage: "Start the Catalyst server",
				Flags: []cli.Flag{
					&cli.StringFlag{Name: "http", Usage: "HTTP listen address", Value: ":8090"},
				},
				Action: serve,
			},
			{
				Name:    "demo-data",
				Aliases: []string{"fake-data"},
				Flags: []cli.Flag{
					&cli.IntFlag{Name: "users", Usage: "Number of fake users to generate", Value: 10},
					&cli.IntFlag{Name: "tickets", Usage: "Number of fake tickets to generate", Value: 100},
				},
				Action: fakeData,
			},
			{
				Name:    "upgrade-test-data",
				Aliases: []string{"default-data"},
				Usage:   "Generate default data for Catalyst",
				Action:  defaultData,
			},
			{
				Name: "admin",
				Commands: []*cli.Command{
					{Name: "create", Action: adminCreate},
					{Name: "delete", Action: adminDelete},
					{Name: "set-password", Action: adminSetPassword},
				},
			},
		},
	}

	ctx := context.Background()
	if err := cmd.Run(ctx, os.Args); err != nil {
		slog.ErrorContext(ctx, "Error running catalyst", "error", err)

		os.Exit(1)
	}
}

func setup(ctx context.Context, command *cli.Command) (*app.App, func(), error) {
	catalyst, cleanup, err := app.New(ctx, "./catalyst_data")
	if err != nil {
		return nil, nil, fmt.Errorf("failed to initialize catalyst: %w", err)
	}

	if appURL := command.String("app-url"); appURL != "" {
		_, err := settings.Update(ctx, catalyst.Queries, func(settings *settings.Settings) {
			settings.Meta.AppURL = appURL
		})
		if err != nil {
			return nil, nil, fmt.Errorf("failed to update app URL: %w", err)
		}
	}

	if err := setFlags(ctx, command.StringSlice("flags"), catalyst.Queries); err != nil {
		return nil, nil, fmt.Errorf("failed to set flags: %w", err)
	}

	return catalyst, cleanup, nil
}

func serve(ctx context.Context, command *cli.Command) error {
	catalyst, cleanup, err := setup(ctx, command)
	if err != nil {
		return fmt.Errorf("failed to setup catalyst: %w", err)
	}

	defer cleanup()

	addr := command.String("http")

	server := &http.Server{
		Addr:        addr,
		Handler:     catalyst,
		ReadTimeout: 10 * time.Minute,
	}

	slog.InfoContext(ctx, "Starting Catalyst server", "address", server.Addr)

	return server.ListenAndServe()
}

func fakeData(ctx context.Context, command *cli.Command) error {
	catalyst, cleanup, err := setup(ctx, command)
	if err != nil {
		return fmt.Errorf("failed to setup catalyst: %w", err)
	}

	defer cleanup()

	if err := data.GenerateDemoData(ctx, catalyst.Queries, command.Int("users"), command.Int("tickets")); err != nil {
		return err
	}

	slog.InfoContext(ctx, "Fake data generated", "users", command.Int("users"), "tickets", command.Int("tickets"))

	return nil
}

func defaultData(ctx context.Context, command *cli.Command) error {
	catalyst, cleanup, err := setup(ctx, command)
	if err != nil {
		return fmt.Errorf("failed to setup catalyst: %w", err)
	}

	defer cleanup()

	if err := data.GenerateUpgradeTestData(ctx, catalyst.Queries); err != nil {
		return fmt.Errorf("failed to generate default data: %w", err)
	}

	slog.InfoContext(ctx, "default data generated successfully")

	return nil
}

func setFlags(ctx context.Context, newFlags []string, queries *sqlc.Queries) error {
	features, err := queries.ListFeatures(ctx, sqlc.ListFeaturesParams{})
	if err != nil {
		return err
	}

	var existingFlags []string

	for _, feature := range features {
		if !slices.Contains(newFlags, feature.Key) {
			if err := queries.DeleteFeature(ctx, feature.Key); err != nil {
				return err
			}

			slog.InfoContext(ctx, "deleted feature", "name", feature.Key)

			continue
		}

		existingFlags = append(existingFlags, feature.Key)
	}

	for _, flag := range newFlags {
		if slices.Contains(existingFlags, flag) {
			continue
		}

		if _, err := queries.CreateFeature(ctx, flag); err != nil {
			return err
		}
	}

	return nil
}
