package app

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

	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/config"
	httpserver "bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/http"
	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/platform"
	redisclient "bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/redis"
	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/service/commitintel"
	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/store"
	mongostore "bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/store/mongo"
	"bit.admedia.com/scm/ad/adpilot-indexing-commit-intel.com/internal/telemetry"
)

const serviceName = "commit-intelligence-service"

type App struct {
	cfg        config.Config
	logger     *slog.Logger
	mongoStore *mongostore.Store
	redis      *redisclient.Client
	publisher  *redisclient.Publisher
	consumer   *redisclient.Consumer
	analysisJobs *store.AnalysisStore
	svc        *commitintel.Service
	server     *http.Server
}

func New() (*App, error) {
	platform.Name = serviceName

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

	platform.Version = cfg.Analysis.ServiceVersion

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

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

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

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

	analysisJobs := store.NewAnalysisStore()
	publisher := redisclient.NewPublisher(redis, cfg.Redis, logger)
	commitService := commitintel.NewService(
		publisher,
		mongoStore,
		commitintel.NewDeltaImpactAnalyzer(),
		analysisJobs,
		commitintel.WithAnalysisWriter(mongoStore),
		commitintel.WithIndexingRunUpdater(mongoStore),
	)
	consumer := redisclient.NewConsumer(redis, cfg, commitService, logger)

	resultReader := &store.CompositeResultReader{
		Mongo: mongoStore,
		Jobs:  analysisJobs,
	}

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

	httpServer := httpserver.New(serviceName, ready, httpserver.DebugAPI{
		Jobs:      analysisJobs,
		Results:   resultReader,
	})

	return &App{
		cfg:          cfg,
		logger:       logger,
		mongoStore:   mongoStore,
		redis:        redis,
		publisher:    publisher,
		consumer:     consumer,
		analysisJobs: analysisJobs,
		svc:          commitService,
		server: &http.Server{
			Addr:              cfg.Address(),
			Handler:           telemetry.HTTPMiddleware(logger, httpServer.Handler()),
			ReadHeaderTimeout: 5 * time.Second,
		},
	}, nil
}

func (a *App) Run(ctx context.Context) error {
	bg := context.Background()
	redisReady := a.redis.Ready(bg)
	mongoReady := a.mongoStore.Ready(bg)

	a.logger.Info("starting service",
		"address", a.cfg.Address(),
		"environment", a.cfg.Environment,
		"status", a.svc.Status(),
		"redis_url", a.cfg.Redis.URL,
		"redis_stream_prefix", a.cfg.Redis.StreamPrefix,
		"redis_ready", redisReady,
		"mongo_uri", a.cfg.Mongo.URI,
		"mongo_database", a.cfg.Mongo.Database,
		"mongo_ready", mongoReady,
		"consumer_group", a.cfg.Redis.ConsumerGroup,
		"analysis_workers", a.cfg.Analysis.Workers,
	)

	var consumerWG sync.WaitGroup
	consumerWG.Add(1)
	go func() {
		defer consumerWG.Done()
		defer telemetry.RecoverPanic(a.logger, "redis-consumer")
		if err := a.consumer.Run(ctx); err != nil && !errors.Is(err, context.Canceled) {
			telemetry.RecordError(a.logger, err, "redis consumer stopped")
		}
	}()

	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():
		consumerWG.Wait()

		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)
		}
		if err := a.mongoStore.Close(shutdownCtx); err != nil {
			return fmt.Errorf("close mongodb: %w", err)
		}
		if err := a.redis.Close(); err != nil {
			return fmt.Errorf("close redis: %w", err)
		}
		a.logger.Info("service stopped")
		return nil
	case err := <-errCh:
		return err
	}
}
