package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"os/signal"

	"github.com/getsentry/sentry-go"

	"github.com/rjNemo/rentease/assets"
	"github.com/rjNemo/rentease/internal/config"
	"github.com/rjNemo/rentease/internal/driver/database"
	"github.com/rjNemo/rentease/internal/driver/logger"
	"github.com/rjNemo/rentease/internal/driver/parser"
	"github.com/rjNemo/rentease/internal/driver/pdf"
	stripeclient "github.com/rjNemo/rentease/internal/driver/stripe"
	bookingRepo "github.com/rjNemo/rentease/internal/repository/booking"
	"github.com/rjNemo/rentease/internal/server"
	"github.com/rjNemo/rentease/internal/service/auth"
	"github.com/rjNemo/rentease/internal/service/booking"
)

func main() {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer cancel()

	baseLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))

	appConfig, err := config.New(ctx)
	if err != nil {
		baseLogger.Error("configuration error", slog.Any("error", err))
	}

	appLogger := logger.New(appConfig.LogLevel)
	slog.SetDefault(appLogger)

	if err := run(ctx, appConfig, appLogger); err != nil {
		appLogger.Error("server exited", slog.Any("error", err))
		os.Exit(1)
	}
}

func run(ctx context.Context, appConfig *config.Config, appLogger *slog.Logger) error {
	// init sentry
	if err := sentry.Init(sentry.ClientOptions{
		Dsn:              appConfig.SentryDsn,
		EnableTracing:    true,
		TracesSampleRate: 1.0,
	}); err != nil {
		return fmt.Errorf("error initializing sentry %w", err)
	}

	// init database
	db, err := database.New(appConfig.DatabaseURL)
	if err != nil {
		return fmt.Errorf("error connecting to the database %w", err)
	}

	if err = database.Migrate(db, &booking.Booking{}, &booking.Item{}, &booking.Payment{}); err != nil {
		return fmt.Errorf("error migrating the database %w", err)
	}

	bookingStore := bookingRepo.NewPgStore(db)

	// build pdf client
	pc, err := pdf.NewPdfClient()
	if err != nil {
		return fmt.Errorf("error starting pdf client %w", err)
	}

	parsingClient := parser.NewBookingAgentParser()

	var stripeClient booking.StripeClient
	if appConfig.StripeSecretKey != "" {
		opts := []stripeclient.Option{}

		client, err := stripeclient.New(appConfig.StripeSecretKey, opts...)
		if err != nil {
			return fmt.Errorf("error creating stripe client: %w", err)
		}
		stripeClient = client
	}

	bookingService, err := booking.NewService(appLogger, bookingStore, parsingClient, pc, stripeClient)
	if err != nil {
		return fmt.Errorf("error creating booking service: %w", err)
	}

	// build authentication service
	as, err := auth.NewService(
		appConfig.SessionSecret,
		appConfig.Admin,
		appConfig.AdminSecret,
		appConfig.APIKey,
	)
	if err != nil {
		return fmt.Errorf("error starting auth service %w", err)
	}

	port := appConfig.Port

	origins := appConfig.Origins

	srv, err := server.New(
		bookingService,
		as,
		config.NewHost(), // TODO: move to the database at some point
		server.WithPort(port),
		server.WithFileSystem(assets.Static),
		server.WithDebug(appConfig.Debug),
		server.WithSecretKey(appConfig.SecretKey),
		server.WithOrigins(origins),
		server.WithStripeWebhookSecret(appConfig.StripeWebhookSecret),
	)
	if err != nil {
		return fmt.Errorf("error starting server %w", err)
	}

	srv.Start(ctx)
	return nil
}
