package main

import (
	"context"
	"errors"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/niels/banking-app/backend/internal/accounts"
	"github.com/niels/banking-app/backend/internal/admin"
	"github.com/niels/banking-app/backend/internal/aml"
	"github.com/niels/banking-app/backend/internal/appsec"
	"github.com/niels/banking-app/backend/internal/audit"
	"github.com/niels/banking-app/backend/internal/authguard"
	"github.com/niels/banking-app/backend/internal/backoffice"
	"github.com/niels/banking-app/backend/internal/beneficiaries"
	"github.com/niels/banking-app/backend/internal/cards"
	"github.com/niels/banking-app/backend/internal/compliance"
	"github.com/niels/banking-app/backend/internal/config"
	"github.com/niels/banking-app/backend/internal/crypto"
	"github.com/niels/banking-app/backend/internal/fx"
	"github.com/niels/banking-app/backend/internal/golive"
	"github.com/niels/banking-app/backend/internal/httpapi"
	"github.com/niels/banking-app/backend/internal/integrations/bankledger"
	"github.com/niels/banking-app/backend/internal/integrations/blockchainanalytics"
	"github.com/niels/banking-app/backend/internal/integrations/cardissuer"
	"github.com/niels/banking-app/backend/internal/integrations/custody"
	"github.com/niels/banking-app/backend/internal/integrations/identityprovider"
	"github.com/niels/banking-app/backend/internal/integrations/sanctions"
	"github.com/niels/banking-app/backend/internal/kyc"
	"github.com/niels/banking-app/backend/internal/ledger"
	"github.com/niels/banking-app/backend/internal/legal"
	"github.com/niels/banking-app/backend/internal/observability"
	"github.com/niels/banking-app/backend/internal/platform/database"
	"github.com/niels/banking-app/backend/internal/platform/logger"
	"github.com/niels/banking-app/backend/internal/platform/migrations"
	"github.com/niels/banking-app/backend/internal/privacy"
	"github.com/niels/banking-app/backend/internal/providers"
	"github.com/niels/banking-app/backend/internal/reconciliation"
	"github.com/niels/banking-app/backend/internal/risk"
	"github.com/niels/banking-app/backend/internal/savings"
	"github.com/niels/banking-app/backend/internal/security"
	"github.com/niels/banking-app/backend/internal/settlement"
	"github.com/niels/banking-app/backend/internal/transfers"
	"github.com/niels/banking-app/backend/internal/users"
	"github.com/niels/banking-app/backend/internal/wallets"
)

func main() {
	cfg, err := config.Load()
	if err != nil {
		slog.Error("configuration error", "error", err)
		os.Exit(1)
	}

	log := logger.New(cfg.App.Environment, cfg.Observability.StructuredLogs)
	slog.SetDefault(log)

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	db, err := database.Connect(ctx, cfg.Database)
	if err != nil {
		log.Error("database connection failed", "error", err)
		os.Exit(1)
	}
	defer db.Close()

	if cfg.Database.RunMigrations {
		result, err := migrations.Run(ctx, db, cfg.Database.MigrationsDir, log)
		if err != nil {
			log.Error("database migrations failed", "error", err, "dir", cfg.Database.MigrationsDir)
			os.Exit(1)
		}
		log.Info("database migrations ready", "applied", result.Applied, "skipped", result.Skipped)
	}

	tokenManager := security.NewTokenManager(cfg.Auth.JWTSecret, cfg.Auth.JWTIssuer, cfg.Auth.AccessTokenTTL)
	secretBox, err := security.NewSecretBox(cfg.Auth.MFASecret)
	if err != nil {
		log.Error("MFA secret box initialization failed", "error", err)
		os.Exit(1)
	}
	metrics := observability.NewRegistry()

	auditRepo := audit.NewRepository(db)
	providerRepo := providers.NewRepository(db)
	providerExecutor := providers.NewExecutor(providers.Controls{
		Timeout:                 cfg.Providers.DefaultTimeout,
		MaxAttempts:             cfg.Providers.MaxAttempts,
		RetryBaseDelay:          cfg.Providers.RetryBaseDelay,
		CircuitFailureThreshold: cfg.Providers.CircuitBreakerFailureThreshold,
		CircuitCooldown:         cfg.Providers.CircuitBreakerCooldown,
		DegradedMode:            cfg.Providers.DegradedMode,
		AuditEnabled:            cfg.Providers.AuditEnabled,
	}, providerRepo, metrics)
	userRepo := users.NewRepository(db)
	authGuardRepo := authguard.NewRepository(db, authguard.Config{
		IdentifierWindow:      cfg.Auth.LoginIdentifierWindow,
		IdentifierMaxFailures: cfg.Auth.LoginIdentifierFailures,
		IPWindow:              cfg.Auth.LoginIPWindow,
		IPMaxFailures:         cfg.Auth.LoginIPFailures,
		LockoutDuration:       cfg.Auth.LoginLockoutDuration,
	})
	bankLedgerProvider := providers.WrapBankLedger(bankledger.NewLocalProvider(), providerExecutor, providers.AdapterOptions{
		ProviderType: "bank_ledger",
		ProviderName: cfg.Providers.BankLedgerName,
		Mode:         providers.AdapterMode(cfg.Providers.BankLedgerMode),
	})
	ledgerRepo := ledger.NewRepository(db)
	sanctionsProvider := providers.WrapSanctionsProvider(sanctions.NewLocalProvider(), providerExecutor, providers.AdapterOptions{
		ProviderType: "sanctions",
		ProviderName: cfg.Providers.SanctionsName,
		Mode:         providers.AdapterMode(cfg.Providers.SanctionsMode),
	})
	adminRepo := admin.NewRepository(db, ledgerRepo).WithSanctions(sanctionsProvider)
	accountRepo := accounts.NewRepository(db, bankLedgerProvider)
	riskRepo := risk.NewRepository(db)
	transferRepo := transfers.NewRepository(db, ledgerRepo).WithRisk(riskRepo).WithSanctions(sanctionsProvider)
	settlementRepo := settlement.NewRepository(db, ledgerRepo)
	beneficiaryRepo := beneficiaries.NewRepository(db)
	cardRepo := cards.NewRepository(db, ledgerRepo).WithRisk(riskRepo)
	walletRepo := wallets.NewRepository(db)
	kycRepo := kyc.NewRepository(db)
	amlRepo := aml.NewRepository(db)
	complianceRepo := compliance.NewRepository(db, amlRepo)
	cryptoRepo := crypto.NewRepository(db)
	savingsRepo := savings.NewRepository(db, ledgerRepo)
	fxRepo := fx.NewRepository(db, ledgerRepo).WithRisk(riskRepo)
	reconciliationRepo := reconciliation.NewRepository(db)
	privacyRepo := privacy.NewRepository(db, auditRepo)
	appsecRepo := appsec.NewRepository(db)
	backofficeRepo := backoffice.NewRepository(db)
	legalRepo := legal.NewRepository(db)
	goLiveRepo := golive.NewRepository(db)
	cardIssuer := providers.WrapCardIssuer(cardissuer.NewLocalIssuer(cfg.Cards.Secret), providerExecutor, providers.AdapterOptions{
		ProviderType: "card_issuer",
		ProviderName: cfg.Providers.CardIssuerName,
		Mode:         providers.AdapterMode(cfg.Providers.CardIssuerMode),
	})
	custodyProvider := providers.WrapCustodyProvider(custody.NewLocalProvider(), providerExecutor, providers.AdapterOptions{
		ProviderType: "custody",
		ProviderName: cfg.Providers.CustodyName,
		Mode:         providers.AdapterMode(cfg.Providers.CustodyMode),
	})
	blockchainAnalyticsProvider := providers.WrapBlockchainAnalytics(blockchainanalytics.NewLocalProvider(), providerExecutor, providers.AdapterOptions{
		ProviderType: "blockchain_analytics",
		ProviderName: cfg.Providers.BlockchainAnalyticsName,
		Mode:         providers.AdapterMode(cfg.Providers.BlockchainAnalyticsMode),
	})
	identityProvider := providers.WrapIdentityProvider(identityprovider.NewLocalProvider(), providerExecutor, providers.AdapterOptions{
		ProviderType: "identity",
		ProviderName: cfg.Providers.IdentityName,
		Mode:         providers.AdapterMode(cfg.Providers.IdentityMode),
	})
	amlService := aml.NewService(amlRepo, sanctionsProvider)
	complianceGuard := compliance.NewGuard(kycRepo, amlRepo)

	router := httpapi.NewRouter(httpapi.Dependencies{
		Config:         cfg,
		Logger:         log,
		DB:             db,
		Metrics:        metrics,
		Tokens:         tokenManager,
		Users:          users.NewHandler(userRepo, auditRepo, tokenManager, secretBox, cfg.Auth.JWTIssuer, cfg.Auth.RefreshTokenTTL, cfg.Auth.StepUpTTL, authGuardRepo, log),
		Accounts:       accounts.NewHandler(accountRepo, auditRepo, log),
		Transfers:      transfers.NewHandler(transferRepo, auditRepo, complianceGuard, log),
		Beneficiaries:  beneficiaries.NewHandler(beneficiaryRepo, auditRepo, log),
		Cards:          cards.NewHandler(cardRepo, auditRepo, cardIssuer, complianceGuard, cfg.Cards.WebhookSecret, log).WithProviderWebhooks(providerRepo, cfg.Providers.CardIssuerName, cfg.Providers.WebhookTolerance),
		Wallets:        wallets.NewHandler(walletRepo, auditRepo, log),
		KYC:            kyc.NewHandler(kycRepo, auditRepo, identityProvider, amlService, cfg.KYC.WebhookSecret, log).WithProviderWebhooks(providerRepo, cfg.Providers.IdentityName, cfg.Providers.WebhookTolerance),
		AML:            aml.NewHandler(amlRepo, auditRepo, log),
		Compliance:     compliance.NewHandler(complianceRepo, auditRepo, sanctionsProvider, log),
		Admin:          admin.NewHandler(adminRepo, auditRepo, log),
		AppSec:         appsec.NewHandler(appsecRepo, auditRepo, log),
		Backoffice:     backoffice.NewHandler(backofficeRepo, auditRepo, log),
		Crypto:         crypto.NewHandler(cryptoRepo, auditRepo, custodyProvider, blockchainAnalyticsProvider, complianceGuard, log),
		Savings:        savings.NewHandler(savingsRepo, auditRepo, complianceGuard, log),
		FX:             fx.NewHandler(fxRepo, auditRepo, complianceGuard, log),
		Reconciliation: reconciliation.NewHandler(reconciliationRepo, auditRepo, log),
		Risk:           risk.NewHandler(riskRepo, auditRepo, log),
		Settlement:     settlement.NewHandler(settlementRepo, auditRepo, log),
		AuthGuard:      authguard.NewHandler(authGuardRepo, auditRepo, log),
		Ledger:         ledger.NewHandler(ledgerRepo, auditRepo, log),
		Audit:          audit.NewHandler(auditRepo),
		Privacy:        privacy.NewHandler(privacyRepo, auditRepo, log),
		Providers:      providers.NewHandler(providerRepo, auditRepo, log),
		Legal:          legal.NewHandler(legalRepo, auditRepo, log),
		GoLive:         golive.NewHandler(goLiveRepo, auditRepo, log),
	})

	server := &http.Server{
		Addr:              cfg.HTTP.Addr,
		Handler:           router,
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       120 * time.Second,
	}

	if cfg.Runtime.EmbeddedWorker && cfg.Features.Payments && cfg.Settlement.SEPAEnabled {
		go settlement.NewWorker(settlementRepo, cfg.Settlement, log).Run(ctx)
	} else {
		log.Info("SEPA settlement worker disabled", "embedded_worker_enabled", cfg.Runtime.EmbeddedWorker, "payments_feature_enabled", cfg.Features.Payments, "sepa_settlement_enabled", cfg.Settlement.SEPAEnabled)
	}

	go func() {
		log.Info("banking API listening", "addr", cfg.HTTP.Addr, "env", cfg.App.Environment)
		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Error("http server failed", "error", err)
			stop()
		}
	}()

	<-ctx.Done()

	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := server.Shutdown(shutdownCtx); err != nil {
		log.Error("graceful shutdown failed", "error", err)
		os.Exit(1)
	}

	log.Info("banking API stopped")
}
