package api

import (
	"context"
	"log/slog"

	"github.com/go-chi/chi/v5"

	"github.com/alexei/docker-watcher/internal/auth"
	"github.com/alexei/docker-watcher/internal/crypto"
	"github.com/alexei/docker-watcher/internal/docker"
	"github.com/alexei/docker-watcher/internal/events"
	"github.com/alexei/docker-watcher/internal/store"
	"github.com/alexei/docker-watcher/internal/webhook"
)

// Server holds all dependencies for the API layer.
type Server struct {
	store        *store.Store
	docker       *docker.Client
	deployer     DeployTriggerer
	webhook      *webhook.Handler
	eventBus     *events.Bus
	encKey       [32]byte
	localAuth    *auth.LocalAuth
	oidcProvider *auth.OIDCProvider
}

// NewServer creates a new API Server with all required dependencies.
func NewServer(
	st *store.Store,
	dockerClient *docker.Client,
	deployer DeployTriggerer,
	webhookHandler *webhook.Handler,
	eventBus *events.Bus,
	encKey [32]byte,
) *Server {
	localAuth := auth.NewLocalAuth(encKey)

	s := &Server{
		store:     st,
		docker:    dockerClient,
		deployer:  deployer,
		webhook:   webhookHandler,
		eventBus:  eventBus,
		encKey:    encKey,
		localAuth: localAuth,
	}

	// Try to initialize OIDC provider from stored settings.
	authSettings, err := st.GetAuthSettings()
	if err == nil && authSettings.AuthMode == "oidc" && authSettings.OIDCIssuerURL != "" {
		s.initOIDCProvider(context.Background(), authSettings)
	}

	return s
}

// initOIDCProvider creates an OIDC provider from settings. Errors are logged, not fatal.
func (s *Server) initOIDCProvider(ctx context.Context, as store.AuthSettings) {
	// Decrypt the OIDC client secret if it's encrypted.
	clientSecret := as.OIDCClientSecret
	if clientSecret != "" {
		if decrypted, err := crypto.Decrypt(s.encKey, clientSecret); err == nil {
			clientSecret = decrypted
		}
		// If decrypt fails, assume it's already plaintext (migration scenario).
	}
	provider, err := auth.NewOIDCProvider(ctx, auth.OIDCConfig{
		IssuerURL:    as.OIDCIssuerURL,
		ClientID:     as.OIDCClientID,
		ClientSecret: clientSecret,
		RedirectURL:  as.OIDCRedirectURL,
	})
	if err != nil {
		slog.Warn("failed to initialize OIDC provider", "error", err)
		return
	}
	s.oidcProvider = provider
	slog.Info("OIDC provider initialized", "issuer", as.OIDCIssuerURL)
}

// Router returns a chi router with all API routes mounted.
func (s *Server) Router() chi.Router {
	r := chi.NewRouter()

	// Global middleware.
	r.Use(recovery)
	r.Use(logging)
	r.Use(cors)
	r.Use(jsonContentType)

	r.Route("/api", func(r chi.Router) {
		// Public auth endpoints (no auth required).
		r.Post("/auth/login", s.login)
		r.Get("/auth/oidc/login", s.oidcLogin)
		r.Get("/auth/oidc/callback", s.oidcCallback)

		// Webhook handler (uses its own secret-based auth).
		r.Mount("/webhook", s.webhook.Route())

		// Protected routes: require valid JWT.
		r.Group(func(r chi.Router) {
			r.Use(auth.Middleware(s.localAuth))

			// Config export (protected — reveals project/infra details).
			r.Get("/config/export", s.exportConfig)

			// Auth management.
			r.Get("/auth/me", s.currentUser)
			r.Get("/auth/settings", s.getAuthSettings)
			r.Put("/auth/settings", s.updateAuthSettings)
			r.Get("/auth/users", s.listUsers)
			r.Post("/auth/users", s.createUser)
			r.Delete("/auth/users/{uid}", s.deleteUser)

			// Project endpoints.
			r.Get("/projects", s.listProjects)
			r.Post("/projects", s.createProject)
			r.Route("/projects/{id}", func(r chi.Router) {
				r.Get("/", s.getProject)
				r.Put("/", s.updateProject)
				r.Delete("/", s.deleteProject)

				// Stage endpoints.
				r.Post("/stages", s.createStage)
				r.Put("/stages/{stage}", s.updateStage)
				r.Delete("/stages/{stage}", s.deleteStage)

				// Stage env override endpoints.
				r.Get("/stages/{stage}/env", s.listStageEnv)
				r.Post("/stages/{stage}/env", s.createStageEnv)
				r.Put("/stages/{stage}/env/{envId}", s.updateStageEnv)
				r.Delete("/stages/{stage}/env/{envId}", s.deleteStageEnv)

				// Instance endpoints.
				r.Get("/stages/{stage}/instances", s.listInstances)
				r.Post("/stages/{stage}/instances", s.deployInstance)
				r.Delete("/stages/{stage}/instances/{iid}", s.removeInstance)

				// Instance control endpoints.
				r.Post("/stages/{stage}/instances/{iid}/stop", s.stopInstance)
				r.Post("/stages/{stage}/instances/{iid}/start", s.startInstance)
				r.Post("/stages/{stage}/instances/{iid}/restart", s.restartInstance)

				// Volume endpoints.
				r.Get("/volumes", s.listVolumes)
				r.Post("/volumes", s.createVolume)
				r.Put("/volumes/{volId}", s.updateVolume)
				r.Delete("/volumes/{volId}", s.deleteVolume)
			})

			// Deploy endpoints.
			r.Get("/deploys", s.listDeploys)
			r.Get("/deploys/{id}/logs", s.streamDeployLogs)

			// SSE endpoint for real-time instance status and deploy events.
			r.Get("/events", s.streamEvents)

			// Quick deploy endpoints.
			r.Post("/deploy/inspect", s.inspectImage)
			r.Post("/deploy/quick", s.quickDeploy)

			// Registry endpoints.
			r.Get("/registries", s.listRegistries)
			r.Post("/registries", s.createRegistry)
			r.Route("/registries/{id}", func(r chi.Router) {
				r.Put("/", s.updateRegistry)
				r.Delete("/", s.deleteRegistry)
				r.Post("/test", s.testRegistry)
				r.Get("/tags/*", s.listRegistryTags)
			})

			// Settings endpoints.
			r.Get("/settings", s.getSettings)
			r.Put("/settings", s.updateSettings)
			r.Get("/settings/webhook-url", s.getWebhookURL)
			r.Post("/settings/regenerate", s.regenerateWebhookSecret)
		})
	})

	return r
}
