package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"runtime/debug"

	"git.netflux.io/rob/octoplex/internal/domain"
	"git.netflux.io/rob/octoplex/internal/event"
	pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
	"git.netflux.io/rob/octoplex/internal/protocol"
	"git.netflux.io/rob/octoplex/internal/terminal"
	"golang.design/x/clipboard"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

var (
	// version is the version of the application.
	version string
	// commit is the commit hash of the application.
	commit string
	// date is the date of the build.
	date string
)

func main() {
	if err := run(); err != nil {
		os.Stderr.WriteString("Error: " + err.Error() + "\n")
		os.Exit(1)
	}
}

func run() error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	g, ctx := errgroup.WithContext(ctx)

	// TODO: logger from config
	fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return fmt.Errorf("open log file: %w", err)
	}
	logger := slog.New(slog.NewTextHandler(fptr, nil))

	bus := event.NewBus(logger)

	var clipboardAvailable bool
	if err = clipboard.Init(); err != nil {
		logger.Warn("Clipboard not available", "err", err)
	} else {
		clipboardAvailable = true
	}

	buildInfo, ok := debug.ReadBuildInfo()
	if !ok {
		return fmt.Errorf("read build info: %w", err)
	}

	conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		return fmt.Errorf("connect to gRPC server: %w", err)
	}
	apiClient := pb.NewInternalAPIClient(conn)
	stream, err := apiClient.Communicate(ctx)
	if err != nil {
		return fmt.Errorf("create gRPC stream: %w", err)
	}

	g.Go(func() error {
		for {
			envelope, recErr := stream.Recv()
			if recErr != nil {
				return fmt.Errorf("receive envelope: %w", recErr)
			}

			evt := envelope.GetEvent()
			if evt == nil {
				logger.Error("Received envelope without event")
				continue
			}

			logger.Debug("Received event", "type", fmt.Sprintf("%T", evt))
			bus.Send(protocol.EventFromProto(evt))
		}
	})

	ui, err := terminal.NewUI(ctx, terminal.Params{
		EventBus: bus,
		Dispatcher: func(cmd event.Command) {
			logger.Info("Command dispatched", "cmd", cmd.Name())
			if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: protocol.CommandToProto(cmd)}}); sendErr != nil {
				logger.Error("Error sending command to gRPC API", "err", sendErr)
			}
		},
		ClipboardAvailable: clipboardAvailable,
		BuildInfo: domain.BuildInfo{
			GoVersion: buildInfo.GoVersion,
			Version:   version,
			Commit:    commit,
			Date:      date,
		},
		Logger: logger.With("component", "ui"),
	})
	if err != nil {
		return fmt.Errorf("start terminal user interface: %w", err)
	}
	defer ui.Close()

	g.Go(func() error { return ui.Run(ctx) })

	if err := g.Wait(); err == terminal.ErrUserClosed {
		return nil
	} else {
		return fmt.Errorf("errgroup.Wait: %w", err)
	}
}