package main

import (
	"context"
	_ "embed"
	"evening_detective_server/internal/app"
	"evening_detective_server/internal/modules/cleaner"
	"evening_detective_server/internal/modules/email_sender"
	"evening_detective_server/internal/modules/file_storage"
	"evening_detective_server/internal/modules/password_generator"
	"evening_detective_server/internal/modules/processor_jwt"
	"evening_detective_server/internal/modules/storytelling"
	"evening_detective_server/internal/repos/actions_repo"
	"evening_detective_server/internal/repos/applications_repo"
	"evening_detective_server/internal/repos/games_repo"
	"evening_detective_server/internal/repos/refresh_tokens_repo"
	"evening_detective_server/internal/repos/scenarios_repo"
	"evening_detective_server/internal/repos/teams_repo"
	"evening_detective_server/internal/repos/users_repo"
	"evening_detective_server/internal/services/file_service"
	"evening_detective_server/internal/services/game_service"
	"evening_detective_server/internal/services/scenarios_service"
	"evening_detective_server/internal/services/ui_service"
	"evening_detective_server/internal/services/users_service"
	proto "evening_detective_server/proto"
	"log"
	"net"
	"net/http"
	"os"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/joho/godotenv"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/swaggest/swgui/v5emb"
)

//go:embed main.swagger.json
var swaggerJSON []byte

func main() {
	_ = godotenv.Load()

	// PostgreSQL
	dbpool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatalf("Unable to create connection pool: %v\n", err)
	}
	defer dbpool.Close()

	// Test request to DB
	var greeting string
	if err = dbpool.QueryRow(context.Background(), "select 'Hello, world!'").Scan(&greeting); err != nil {
		log.Fatalf("QueryRow failed: %v\n", err)
	}

	usersRepo := users_repo.NewUserRepo(dbpool)
	passwordGenerator := password_generator.NewGenerator(8)
	emailSender := email_sender.NewSender(
		os.Getenv("SMTP_HOST"),
		os.Getenv("SMTP_PORT"),
		os.Getenv("SMTP_USER"),
		os.Getenv("SMTP_PASSWORD"),
	)
	processorJWT := processor_jwt.NewProcessor(os.Getenv("JWT_SECRET"))
	refreshTokensRepo := refresh_tokens_repo.NewRefreshTokensRepo(dbpool)
	usersService := users_service.NewUsersService(usersRepo, passwordGenerator, emailSender, processorJWT, refreshTokensRepo)
	uiService := ui_service.NewUiService()
	fileStorage, err := file_storage.NewRustFSStorage(
		os.Getenv("S3_HOST"),
		os.Getenv("S3_USER"),
		os.Getenv("S3_PASSWORD"),
		os.Getenv("S3_BUCKET"),
	)
	if err != nil {
		log.Fatalf("Unable to create connection rustfs: %v\n", err)
	}
	fileService := file_service.NewFileService(fileStorage)
	scenariosRepo := scenarios_repo.NewScenariosRepo(dbpool)
	cleaner := cleaner.NewCleaner()
	scenarioService := scenarios_service.NewScenarioService(
		scenariosRepo,
		cleaner,
		os.Getenv("FILE_PREFIX_DOMAIN"),
	)
	gameRepo := games_repo.NewGamesRepo(dbpool)
	teamRepo := teams_repo.NewTeamsRepo(dbpool)
	storyteller := storytelling.NewStory(cleaner)
	actionsRepo := actions_repo.NewActionsRepo(dbpool)
	applicationsRepo := applications_repo.NewApplicationsRepo(dbpool)
	gameService := game_service.NewGameService(
		gameRepo,
		teamRepo,
		scenariosRepo,
		scenarioService,
		os.Getenv("FILE_PREFIX_DOMAIN"),
		passwordGenerator,
		storyteller,
		actionsRepo,
		applicationsRepo,
		cleaner,
	)

	// Create a listener on TCP port
	lis, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalln("Failed to listen:", err)
	}

	// Create a gRPC server object
	s := grpc.NewServer(
		grpc.UnaryInterceptor(
			processor_jwt.NewAuthorizationInterceptor(
				map[string]bool{
					"/crabs.evening_detective_server.EveningDetectiveServer/Ping":                true,
					"/crabs.evening_detective_server.EveningDetectiveServer/Echo":                true,
					"/crabs.evening_detective_server.EveningDetectiveServer/Signup":              true,
					"/crabs.evening_detective_server.EveningDetectiveServer/RefreshPassword":     true,
					"/crabs.evening_detective_server.EveningDetectiveServer/Login":               true,
					"/crabs.evening_detective_server.EveningDetectiveServer/Refresh":             true,
					"/crabs.evening_detective_server.EveningDetectiveServer/DownloadFile":        true,
					"/crabs.evening_detective_server.EveningDetectiveServer/GetScenario":         true,
					"/crabs.evening_detective_server.EveningDetectiveServer/GetScenariosCatalog": true,
					"/crabs.evening_detective_server.EveningDetectiveServer/GetTeamStory":        true,
					"/crabs.evening_detective_server.EveningDetectiveServer/AddTeamAction":       true,
				},
				processorJWT,
			),
		),
	)
	// Attach the Greeter service to the server
	proto.RegisterEveningDetectiveServerServer(
		s,
		app.NewServer(
			usersService,
			uiService,
			fileService,
			scenarioService,
			gameService,
		),
	)

	// Serve gRPC server
	log.Println("Serving gRPC on 0.0.0.0:8080")
	go func() {
		log.Fatalln(s.Serve(lis))
	}()

	// Create a client connection to the gRPC server we just started
	// This is where the gRPC-Gateway proxies the requests
	conn, err := grpc.NewClient(
		"0.0.0.0:8080",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalln("Failed to dial server:", err)
	}

	gwmux := runtime.NewServeMux()
	// Register Greeter
	err = proto.RegisterEveningDetectiveServerHandler(context.Background(), gwmux, conn)
	if err != nil {
		log.Fatalln("Failed to register gateway:", err)
	}

	mainMux := http.NewServeMux()

	docsHandler := v5emb.New("API", "/swagger/doc.json", "/docs/")
	mainMux.Handle("/docs/", docsHandler)
	mainMux.HandleFunc("/swagger/doc.json", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write(swaggerJSON)
	})
	mainMux.Handle("/api/", gwmux)

	gwServer := &http.Server{
		Addr:    ":8090",
		Handler: cors(mainMux),
	}

	log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
	log.Println("Serving docs on http://0.0.0.0:8090/docs")
	log.Fatalln(gwServer.ListenAndServe())
}

func cors(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
		w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, ResponseType, X-Id, X-Password")
		if r.Method == "OPTIONS" {
			return
		}
		h.ServeHTTP(w, r)
	})
}
