package app

import (
	"context"
	"database/sql"
	"fmt"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	chimiddleware "github.com/go-chi/chi/v5/middleware"
	"maintainarr/internal/config"
	"maintainarr/internal/db"
	"maintainarr/internal/handlers"
	localmiddleware "maintainarr/internal/middleware"
	"maintainarr/internal/models"
	"maintainarr/internal/services"
	"maintainarr/internal/views"
)

type App struct {
	config    config.Config
	db        *sql.DB
	server    *http.Server
	scheduler *services.SchedulerService
}

func New() (*App, error) {
	cfg := config.Load()

	database, err := db.Open(cfg.DatabasePath)
	if err != nil {
		return nil, err
	}

	repo := services.NewRepository(database)
	auth := services.NewAuthService(cfg.OrgName)
	crypto, err := services.NewCryptoService(cfg.EncryptionKey)
	if err != nil {
		return nil, err
	}
	sessions := services.NewSessionService(cfg.SessionKey)
	nodes := services.NewNodeService(database, crypto)
	renderer, err := views.NewRenderer()
	if err != nil {
		return nil, err
	}

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

	org, err := repo.EnsureOrganization(ctx, cfg.OrgName, cfg.DefaultTheme)
	if err != nil {
		return nil, err
	}
	if org.ThemeMode == "" {
		if err := repo.UpdateOrganizationTheme(ctx, org.Theme, cfg.DefaultMode); err != nil {
			return nil, err
		}
		org.ThemeMode = cfg.DefaultMode
	}
	if err := repo.SeedDefaults(ctx, org.ID); err != nil {
		return nil, err
	}

	if err := repo.EnsurePrimaryAdmin(ctx); err != nil {
		return nil, err
	}

	handler := handlers.New(repo, auth, sessions, nodes, renderer, org, cfg.BaseURL)
	router := chi.NewRouter()
	router.Use(chimiddleware.RequestID)
	router.Use(chimiddleware.RealIP)
	router.Use(chimiddleware.Recoverer)
	router.Use(chimiddleware.Logger)

	router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
	router.Get("/", handler.Home)
	router.Get("/login", handler.LoginPage)
	router.Post("/login", handler.Login)
	router.Get("/login/otp", handler.LoginOTPPage)
	router.Post("/login/otp", handler.LoginOTP)
	router.Get("/register", handler.RegisterPage)
	router.Post("/register", handler.Register)
	router.Get("/logout", handler.Logout)

	router.Group(func(protected chi.Router) {
		protected.Use(localmiddleware.RequireAuth(sessions, repo))
		protected.Get("/setup-2fa", handler.SetupOTPPage)
		protected.Post("/setup-2fa", handler.SetupOTP)
		protected.Get("/dashboard", handler.Dashboard)
		protected.Get("/dashboard/nodes", handler.DashboardNodes)
		protected.Get("/nodes/{nodeID}", handler.NodeOverview)
		protected.Get("/nodes/{nodeID}/stats", handler.NodeStatsAPI)
		protected.Get("/nodes/{nodeID}/console", handler.NodeConsole)
		protected.Get("/nodes/{nodeID}/console/ws", handler.NodeConsoleWebSocket)
		protected.Get("/groups", handler.GroupsPage)
		protected.Get("/automations", handler.AutomationsPage)
		protected.Get("/updates", handler.UpdatesPage)
		protected.Get("/uptime", handler.UptimePage)
		protected.Get("/settings", handler.SettingsPage)
		protected.Get("/settings/user", handler.UserSettingsPage)
		protected.Post("/settings/theme", handler.UpdateTheme)
		protected.Post("/settings/user/theme", handler.UpdateUserTheme)
		protected.Post("/settings/user/password", handler.UpdateUserPassword)
		protected.Post("/settings/user/2fa/{action}", handler.UpdateUserOTP)

		protected.Group(func(editor chi.Router) {
			editor.Use(localmiddleware.RequireRole(models.RoleEditor))
			editor.Post("/groups", handler.CreateGroup)
			editor.Post("/groups/{groupID}", handler.UpdateGroup)
			editor.Post("/nodes", handler.CreateNode)
			editor.Post("/nodes/{nodeID}", handler.UpdateNode)
			editor.Post("/nodes/{nodeID}/actions/{action}", handler.NodeAction)
			editor.Post("/nodes/{nodeID}/commands", handler.NodeQuickCommand)
			editor.Post("/nodes/{nodeID}/delete", handler.DeleteNode)
			editor.Post("/automations", handler.CreateAutomation)
			editor.Post("/updates/scan", handler.ScanAllUpdates)
			editor.Post("/updates/apply", handler.ApplyAllUpdates)
			editor.Post("/updates/settings/window", handler.UpdateGlobalUpdateWindow)
			editor.Post("/updates/nodes/{nodeID}/policy", handler.UpdateNodePolicy)
			editor.Post("/updates/nodes/{nodeID}/scan", handler.ScanNodeUpdates)
			editor.Post("/updates/nodes/{nodeID}/apply", handler.ApplyNodeUpdates)
			editor.Post("/updates/groups/{groupID}/policy", handler.UpdateGroupPolicy)
			editor.Post("/updates/groups/{groupID}/scan", handler.ScanGroupUpdates)
			editor.Post("/updates/groups/{groupID}/apply", handler.ApplyGroupUpdates)
			editor.Post("/uptime/run", handler.RunUptimeChecks)
		})
	})

	scheduler := services.NewSchedulerService(database, nodes, cfg.LogArchiveDir)
	if err := scheduler.Start(context.Background(), org.ID, cfg.RefreshCron); err != nil {
		return nil, err
	}

	return &App{
		config: cfg,
		db:     database,
		server: &http.Server{
			Addr:              cfg.Address,
			Handler:           router,
			ReadHeaderTimeout: 10 * time.Second,
		},
		scheduler: scheduler,
	}, nil
}

func (a *App) Run() error {
	fmt.Printf("Maintainarr listening on %s\n", a.config.Address)
	return a.server.ListenAndServe()
}
