package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"strings"

	"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl/cmdpacks"

	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	tea "github.com/charmbracelet/bubbletea"
	bus "github.com/lmika/events"
	"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
	"lmika.dev/cmd/dynamo-browse/internal/common/ui/logging"
	"lmika.dev/cmd/dynamo-browse/internal/common/ui/osstyle"
	"lmika.dev/cmd/dynamo-browse/internal/common/workspaces"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models/queryexpr"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/dynamo"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/inputhistorystore"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/pasteboardprovider"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/settingstore"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/workspacestore"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/inputhistory"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/itemrenderer"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/jobs"
	keybindings_service "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/keybindings"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/viewsnapshot"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles"
)

func main() {
	var flagTable = flag.String("t", "", "dynamodb table name")
	var flagLocal = flag.String("local", "", "local endpoint")
	var flagDebug = flag.String("debug", "", "file to log debug messages")
	var flagRO = flag.Bool("ro", false, "enable readonly mode")
	var flagDefaultLimit = flag.Int("default-limit", 0, "default limit for queries and scans")
	var flagWorkspace = flag.String("w", "", "workspace file")
	var flagQuery = flag.String("q", "", "run query")
	var flagExtDir = flag.String("ext-dir", "$HOME/.config/dynamo-browse/ext:$HOME/.config/dynamo-browse/.", "directory to search for extensions")
	flag.Parse()

	ctx := context.Background()

	cfg, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		fmt.Fprintf(os.Stderr, "cannot load AWS config: %v", err)
		os.Exit(1)
	}

	closeFn := logging.EnableLogging(*flagDebug)
	defer closeFn()

	wsManager := workspaces.New(workspaces.MetaInfo{Command: "dynamo-browse"})
	ws, err := wsManager.OpenOrCreate(*flagWorkspace)
	if err != nil {
		fmt.Fprintf(os.Stderr, "cannot create workspace: %v", ws)
		os.Exit(1)
	}
	defer ws.Close()

	var dynamoClient *dynamodb.Client
	if *flagLocal != "" {
		host, port, err := net.SplitHostPort(*flagLocal)
		if err != nil {
			fmt.Fprintf(os.Stderr, "invalid address '%v': %v", *flagLocal, err)
			os.Exit(1)
		}
		if host == "" {
			host = "localhost"
		}
		if port == "" {
			port = "8000"
		}
		dynamoClient = dynamodb.NewFromConfig(cfg,
			dynamodb.WithEndpointResolver(dynamodb.EndpointResolverFromURL(fmt.Sprintf("http://%v:%v", host, port))))
	} else {
		dynamoClient = dynamodb.NewFromConfig(cfg)
	}

	eventBus := bus.New()

	uiStyles := styles.DefaultStyles
	dynamoProvider := dynamo.NewProvider(dynamoClient)
	resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
	settingStore := settingstore.New(ws)
	inputHistoryStore := inputhistorystore.NewInputHistoryStore(ws)
	pasteboardProvider := pasteboardprovider.New()

	if *flagRO {
		if err := settingStore.SetReadOnly(*flagRO); err != nil {
			fmt.Fprintf(os.Stderr, "unable to set read-only mode: %v", err)
			os.Exit(1)
		}
	}
	if *flagDefaultLimit > 0 {
		if err := settingStore.SetDefaultLimit(*flagDefaultLimit); err != nil {
			fmt.Fprintf(os.Stderr, "unable to set default limit: %v", err)
			os.Exit(1)
		}
	}

	tableService := tables.NewService(dynamoProvider, settingStore)
	workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
	itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
	jobsService := jobs.NewService(eventBus)
	inputHistoryService := inputhistory.New(inputHistoryStore)

	state := controllers.NewState()
	jobsController := controllers.NewJobsController(jobsService, eventBus, false)
	tableReadController := controllers.NewTableReadController(
		state,
		tableService,
		workspaceService,
		itemRendererService,
		jobsController,
		inputHistoryService,
		eventBus,
		pasteboardProvider,
		*flagTable,
	)
	tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore)
	columnsController := controllers.NewColumnsController(tableReadController, eventBus)
	exportController := controllers.NewExportController(state, tableService, jobsController, columnsController, pasteboardProvider)
	settingsController := controllers.NewSettingsController(settingStore, eventBus)
	keyBindings := keybindings.Default()

	if *flagQuery != "" {
		if *flagTable == "" {
			fmt.Fprintf(os.Stderr, "-t will need to be set for -q")
			os.Exit(1)
		}

		ctx := context.Background()

		query, err := queryexpr.Parse(*flagQuery)
		if err != nil {
			fmt.Fprintf(os.Stderr, "query: %v", err)
			os.Exit(1)
		}

		ti, err := tableService.Describe(ctx, *flagTable)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot describe table: %v", err)
			os.Exit(1)
		}

		rs, err := tableService.ScanOrQuery(ctx, ti, query, nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot execute query: %v", err)
			os.Exit(1)
		}
		if err := exportController.ExportToWriter(os.Stdout, rs); err != nil {
			fmt.Fprintf(os.Stderr, "cannot export results of query: %v", err)
			os.Exit(1)
		}
		return
	}

	keyBindingService := keybindings_service.NewService(keyBindings)
	keyBindingController := controllers.NewKeyBindingController(keyBindingService, nil)

	stdCommands := cmdpacks.NewStandardCommands(
		tableService,
		state,
		tableReadController,
		tableWriteController,
		exportController,
		keyBindingController,
		pasteboardProvider,
		settingsController,
	)

	commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)
	if err != nil {
		fmt.Fprintf(os.Stderr, "cannot setup command controller: %v", err)
		os.Exit(1)
	}
	commandController.SetCommandCompletionProvider(columnsController)

	model := ui.NewModel(
		tableReadController,
		tableWriteController,
		columnsController,
		exportController,
		settingsController,
		jobsController,
		itemRendererService,
		commandController,
		eventBus,
		keyBindingController,
		pasteboardProvider,
		keyBindings,
	)
	commandController.SetUIStateProvider(&model)

	// Pre-determine if layout has dark background.  This prevents calls for creating a list to hang.
	osstyle.DetectCurrentScheme()

	p := tea.NewProgram(model, tea.WithAltScreen())

	jobsController.SetMessageSender(p.Send)

	if err := commandController.LoadExtensions(context.Background(), strings.Split(*flagExtDir, string(os.PathListSeparator))); err != nil {
		fmt.Printf("Unable to load extensions: %v", err)
	}

	go commandController.StartMessageSender(p.Send)

	log.Println("launching")
	if err := p.Start(); err != nil {
		fmt.Printf("Alas, there's been an error: %v", err)
		os.Exit(1)
	}
}
