package app

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

	"go.uber.org/zap"

	"productier/apps/backend/internal/authsession"
	"productier/apps/backend/internal/filestorage"
	"productier/apps/backend/internal/httpapi"
	"productier/apps/backend/internal/mailruntime"
	"productier/apps/backend/internal/store"
)

type App struct {
	server          *httpapi.Server
	port            string
	shutdownTimeout time.Duration
	stopMailRuntime context.CancelFunc
}

func New(logger *zap.Logger) (*App, error) {
	runtimeConfig, err := loadRuntimeConfig()
	if err != nil {
		return nil, err
	}

	var dataStore store.Store
	databaseURL := os.Getenv("DATABASE_URL")
	if databaseURL != "" {
		persistentStore, err := store.NewPostgresStore(databaseURL, runtimeConfig.mode)
		if err != nil {
			return nil, err
		}
		dataStore = persistentStore
	} else {
		if err := validateStoreRuntimeMode(runtimeConfig.mode, inMemoryStoreAllowed()); err != nil {
			return nil, err
		}
		dataStore = store.NewSeededState(runtimeConfig.mode)
	}

	mailService, err := mailruntime.New(dataStore, logger, runtimeConfig.mailSecret)
	if err != nil {
		return nil, err
	}

	files, err := filestorage.NewFromEnv()
	if err != nil {
		return nil, err
	}
	probeCtx, cancelProbe := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancelProbe()
	if err := files.Probe(probeCtx); err != nil {
		return nil, fmt.Errorf("file storage startup probe failed: %w", err)
	}

	mailRuntimeCtx, stopMailRuntime := context.WithCancel(context.Background())
	mailService.Start(mailRuntimeCtx)

	return &App{
		server: httpapi.NewServer(
			dataStore,
			authsession.NewClient(runtimeConfig.authServiceURL),
			mailService,
			files,
			runtimeConfig.mode,
			runtimeConfig.corsAllowOrigins,
			runtimeConfig.metricsAuthToken,
			logger,
		),
		port:            runtimeConfig.apiPort,
		shutdownTimeout: runtimeConfig.shutdownTimeout,
		stopMailRuntime: stopMailRuntime,
	}, nil
}

func (a *App) Run() error {
	return a.RunContext(context.Background())
}

func (a *App) RunContext(ctx context.Context) error {
	httpServer := &http.Server{
		Addr:              fmt.Sprintf(":%s", a.port),
		Handler:           a.server.Engine(),
		ReadHeaderTimeout: 10 * time.Second,
		ReadTimeout:       30 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       60 * time.Second,
	}

	serverErr := make(chan error, 1)
	go func() {
		err := httpServer.ListenAndServe()
		if err != nil && !errors.Is(err, http.ErrServerClosed) {
			serverErr <- err
		}
		close(serverErr)
	}()

	select {
	case <-ctx.Done():
		if a.stopMailRuntime != nil {
			a.stopMailRuntime()
		}
		shutdownCtx, cancel := context.WithTimeout(context.Background(), a.shutdownTimeout)
		defer cancel()
		if err := httpServer.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
			return fmt.Errorf("shutdown api server: %w", err)
		}
		if err, ok := <-serverErr; ok && err != nil {
			return fmt.Errorf("run api server: %w", err)
		}
		return nil
	case err, ok := <-serverErr:
		if a.stopMailRuntime != nil {
			a.stopMailRuntime()
		}
		if !ok || err == nil {
			return nil
		}
		return fmt.Errorf("run api server: %w", err)
	}
}

func validateStoreRuntimeMode(mode string, allowInMemory bool) error {
	if mode == "development" || allowInMemory {
		return nil
	}

	return fmt.Errorf("DATABASE_URL is required when APP_ENV=%q (set ALLOW_INMEMORY_STORE=true only for temporary non-production testing)", mode)
}

func inMemoryStoreAllowed() bool {
	raw := strings.TrimSpace(strings.ToLower(os.Getenv("ALLOW_INMEMORY_STORE")))
	switch raw {
	case "1", "true", "yes", "on":
		return true
	default:
		return false
	}
}
