package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"

	"ticket-pimp/internal/controller"
	"ticket-pimp/internal/domain"
	"ticket-pimp/internal/external"

	"ticket-pimp/client/discord"
	"ticket-pimp/client/telegram"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/jackc/pgx/v5/stdlib"
	"github.com/pkg/errors"
	migrate "github.com/rubenv/sql-migrate"
	"golang.org/x/sync/errgroup"
)

const migrationfile = "../internal/storage/migrate"

func main() {
	log.Print("started")

	env := flag.Int("env", -1, "0 for development env file, 1 for production environment run, missing flag for build")
	flag.Parse()
	var envPath string

	switch *env {
	case 0:
		envPath = ".env"
	case 1:
		envPath = "../docker/prod.env"
	default:
		envPath = "prod.env"
	}

	config := domain.InitConfig(envPath)
	run(&config)
}

func Go(ctx context.Context, fns ...func(context.Context) error) error {
	group, ctx := errgroup.WithContext(ctx)

	for _, fn := range fns {
		fn := fn
		group.Go(func() error {
			return fn(ctx)
		})
	}

	return group.Wait()
}

func applyMigrations(connString string) {
	// Apply migrations:

	dbConnConfig, err := pgxpool.ParseConfig(connString)
	if err != nil {
		log.Fatalf("unable to parse connString: %v", err)
	}

	migrations := &migrate.FileMigrationSource{
		Dir: migrationfile,
	}

	db := stdlib.OpenDB(*dbConnConfig.ConnConfig)

	const dialect = "postgres"
	n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
	if err != nil {
		log.Fatalf("unable to handle migrations: %v", err)
	}
	fmt.Printf("Applied %d migrations!\n", n)

	err = db.Close()
	if err != nil {
		log.Fatal("unable to close db connection")
	}
}

func run(c *domain.Config) {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM)
	defer cancel()

	// -- DB connection init -- START
	connString := fmt.Sprintf(
		"postgresql://%s:%s@%s:%s/%s",
		c.DB.User, c.DB.Pass, c.DB.Host, c.DB.Port, c.DB.Name,
	)
	conn, err := pgxpool.New(
		ctx,
		connString)
	if err != nil {
		log.Fatalf("DB connection failed: %v", err)
	}

	// Apply migrations:
	applyMigrations(connString)

	// Init services instances:
	git := external.NewGit(c.Git)
	cloud := external.NewCloud(c.Cloud)
	coda := external.NewCoda(c.Coda)

	// Controller instance init:
	controller := controller.NewApp(
		git,
		cloud,
		coda,
		conn,
		c,
	)

	Go(ctx,
		func(ctx context.Context) error {
			opts := discord.DiscordOptions{
				Controller: controller,
				Config:     c,
			}
			if err := discord.Run(c, opts); err != nil {
				return errors.Errorf("discord bot cannot be runned: %v", err)
			}
			return nil
		},
		func(ctx context.Context) error {
			opts := telegram.TelegramOptions{
				GitService:   git,
				CloudService: cloud,
				Coda:         coda,
				AppConfig:    c,
				Controller:   controller,
			}

			if err := telegram.Run(ctx, opts); err != nil {
				return errors.Errorf("telegram bot cannot be runned: %v", err)
			}
			return nil
		},
	)
}
