package app

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"log"
	"net/http"
	"os"
	"time"

	"git.az-it.net/az/morz-infoboard/server/backend/internal/config"
	"git.az-it.net/az/morz-infoboard/server/backend/internal/db"
	"git.az-it.net/az/morz-infoboard/server/backend/internal/httpapi"
	"git.az-it.net/az/morz-infoboard/server/backend/internal/mqttnotifier"
	"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
)

type App struct {
	Config    config.Config
	server    *http.Server
	notifier  *mqttnotifier.Notifier
	authStore *store.AuthStore
	logger    *log.Logger
}

func New() (*App, error) {
	cfg := config.Load()
	logger := log.New(os.Stdout, "backend ", log.LstdFlags|log.LUTC)

	// Ensure upload directory exists.
	if err := os.MkdirAll(cfg.UploadDir, 0755); err != nil {
		return nil, err
	}

	// Connect to database and run migrations.
	pool, err := db.Connect(context.Background(), cfg.DatabaseURL, logger)
	if err != nil {
		return nil, err
	}

	// Status store (existing in-memory/file store).
	statusStore, err := httpapi.NewStoreFromConfig(cfg.StatusStorePath)
	if err != nil {
		pool.Close()
		return nil, err
	}

	// Domain stores.
	tenants := store.NewTenantStore(pool.Pool)
	screens := store.NewScreenStore(pool.Pool)
	media := store.NewMediaStore(pool.Pool)
	playlists := store.NewPlaylistStore(pool.Pool)
	authStore := store.NewAuthStore(pool.Pool)

	// Ensure admin user exists — generate a random password if none is configured.
	adminPassword := cfg.AdminPassword
	if adminPassword == "" {
		buf := make([]byte, 16)
		if _, err := rand.Read(buf); err != nil {
			pool.Close()
			return nil, err
		}
		adminPassword = hex.EncodeToString(buf)
		logger.Printf("event=admin_password_generated password=%s", adminPassword)
	}
	if err := authStore.EnsureAdminUser(context.Background(), cfg.DefaultTenantSlug, adminPassword); err != nil {
		logger.Printf("event=ensure_admin_user_failed err=%v", err)
		// Non-fatal: server starts even if admin setup fails.
	}

	// MQTT notifier (no-op when broker not configured).
	notifier := mqttnotifier.New(cfg.MQTTBroker, cfg.MQTTUsername, cfg.MQTTPassword)
	if cfg.MQTTBroker != "" {
		logger.Printf("event=mqtt_notifier_enabled broker=%s", cfg.MQTTBroker)
	} else {
		logger.Printf("event=mqtt_notifier_disabled reason=no_broker_configured")
	}

	handler := httpapi.NewRouter(httpapi.RouterDeps{
		StatusStore:   statusStore,
		TenantStore:   tenants,
		ScreenStore:   screens,
		MediaStore:    media,
		PlaylistStore: playlists,
		AuthStore:     authStore,
		Notifier:      notifier,
		Config:        cfg,
		UploadDir:     cfg.UploadDir,
		Logger:        logger,
	})

	return &App{
		Config:    cfg,
		server:    &http.Server{Addr: cfg.HTTPAddress, Handler: handler},
		notifier:  notifier,
		authStore: authStore,
		logger:    logger,
	}, nil
}

func (a *App) Run() error {
	defer a.notifier.Close()

	// Session-Cleanup: expired sessions werden stündlich aus der DB entfernt.
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go func() {
		ticker := time.NewTicker(1 * time.Hour)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				if err := a.authStore.CleanExpiredSessions(ctx); err != nil {
					a.logger.Printf("event=session_cleanup_failed err=%v", err)
				} else {
					a.logger.Printf("event=session_cleanup_ok")
				}
			case <-ctx.Done():
				return
			}
		}
	}()

	err := a.server.ListenAndServe()
	if errors.Is(err, http.ErrServerClosed) {
		return nil
	}
	return err
}
