package app

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"net/http"
	"time"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/client"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/config"
	httpserver "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/http"
	gitprovider "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/git"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/platform"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/purge"
	redisclient "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/redis"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/service"
	webhookpkg "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/webhook"
	mongostore "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/store/mongo"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/telemetry"
)

const (
	serviceName              = "repo-sync-service"
	shutdownSyncDrainTimeout = 30 * time.Second
)

// App wires dependencies and runs the HTTP server.
type App struct {
	cfg      config.Config
	logger   *slog.Logger
	metadata *mongostore.Store
	purger   *purge.PipelinePurger
	redis    *redisclient.Client
	svc      *service.SyncService
	server   *http.Server
}

// New creates the application container.
func New() (*App, error) {
	platform.Name = serviceName

	cfg, err := config.Load(serviceName, 6001)
	if err != nil {
		return nil, err
	}

	logger := telemetry.NewLogger(telemetry.LoggerConfig{
		ServiceName: serviceName,
		Level:       cfg.LogLevel,
		LogFile:     cfg.LogFile,
	})
	slog.SetDefault(logger)

	metadata, err := mongostore.New(cfg.Mongo)
	if err != nil {
		return nil, fmt.Errorf("initialize mongodb: %w", err)
	}

	indexCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()
	if err := metadata.EnsureIndexes(indexCtx); err != nil {
		_ = metadata.Close(indexCtx)
		return nil, fmt.Errorf("ensure mongodb indexes: %w", err)
	}

	redis, err := redisclient.New(cfg.Redis)
	if err != nil {
		_ = metadata.Close(context.Background())
		return nil, fmt.Errorf("initialize redis: %w", err)
	}

	publisher := redisclient.NewPublisher(redis, cfg.Redis, logger)

	gitRunner, err := gitprovider.NewShellRunner(cfg, logger)
	if err != nil {
		_ = metadata.Close(context.Background())
		return nil, fmt.Errorf("initialize git runner: %w", err)
	}
	gitProv := gitprovider.NewProvider(cfg, gitRunner, logger)
	syncService := service.NewSyncService(gitProv, publisher, metadata, logger)

	purger, err := purge.NewPipelinePurger(cfg.Pipeline)
	if err != nil {
		_ = metadata.Close(context.Background())
		return nil, fmt.Errorf("initialize pipeline purger: %w", err)
	}
	embeddingClient := client.NewEmbeddingEngineClient(
		cfg.ForceSync.EmbeddingEngineURL,
		cfg.ForceSync.GatewayInternalSecret,
	)
	syncService.ConfigureForceSync(service.ForceSyncDeps{
		Config:    cfg,
		Purger:    purger,
		Embedding: embeddingClient,
	})

	ready := func() bool {
		ctx := context.Background()
		return metadata.Ready(ctx) && redis.Ready(ctx)
	}

	webhookHandler := webhookpkg.NewHandler(cfg.Bitbucket.WebhookSecret, cfg.Environment, syncService, logger)
	httpServer := httpserver.New(
		serviceName,
		ready,
		syncService,
		syncService,
		httpserver.ImportAcceptorFromSync(syncService),
		syncService,
		syncService,
		webhookHandler,
		cfg.Gateway.InternalServiceSecret,
	)

	return &App{
		cfg:      cfg,
		logger:   logger,
		metadata: metadata,
		purger:   purger,
		redis:    redis,
		svc:      syncService,
		server: &http.Server{
			Addr:              cfg.Address(),
			Handler:           telemetry.HTTPMiddleware(logger, httpServer.Handler()),
			ReadHeaderTimeout: 5 * time.Second,
		},
	}, nil
}

// Run starts the HTTP server until context cancellation or error.
func (a *App) Run(ctx context.Context) error {
	bg := context.Background()
	redisReady := a.redis.Ready(bg)
	mongoReady := a.metadata.Ready(bg)

	a.logger.Info("starting service",
		"address", a.cfg.Address(),
		"environment", a.cfg.Environment,
		"redis_url", a.cfg.Redis.URL,
		"redis_stream_prefix", a.cfg.Redis.StreamPrefix,
		"redis_ready", redisReady,
		"mongo_uri", a.cfg.Mongo.URI,
		"mongodb_database", a.cfg.Mongo.Database,
		"mongodb_ready", mongoReady,
		"git_workspace_path", a.cfg.Git.WorkspacePath,
		"bitbucket_base_url", a.cfg.Bitbucket.BaseURL,
		"bitbucket_auth_configured", a.cfg.Bitbucket.Username != "" && a.cfg.Bitbucket.Token != "",
		"status", a.svc.Status(),
	)

	errCh := make(chan error, 1)
	go func() {
		a.logger.Info("http server listening", "address", a.cfg.Address())
		if err := a.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			errCh <- err
		}
	}()

	select {
	case <-ctx.Done():
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if err := a.server.Shutdown(shutdownCtx); err != nil {
			return fmt.Errorf("shutdown server: %w", err)
		}

		drainCtx, drainCancel := context.WithTimeout(context.Background(), shutdownSyncDrainTimeout)
		defer drainCancel()
		a.svc.WaitForSyncs(drainCtx)
		if remaining := a.svc.ActiveSyncCount(); remaining > 0 {
			a.logger.Warn("shutdown proceeding with in-flight syncs", "active_syncs", remaining)
		}

		if err := a.redis.Close(); err != nil {
			return fmt.Errorf("close redis: %w", err)
		}
		if err := a.metadata.Close(shutdownCtx); err != nil {
			return fmt.Errorf("close mongodb: %w", err)
		}
		if a.purger != nil {
			if err := a.purger.Close(shutdownCtx); err != nil {
				return fmt.Errorf("close pipeline mongodb: %w", err)
			}
		}
		a.logger.Info("service stopped")
		return nil
	case err := <-errCh:
		return err
	}
}
