package http

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

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

// Server represents the HTTP server
type Server struct {
	server     *http.Server
	logger     *zap.Logger
	config     *config.Config
	mux        *http.ServeMux
	middleware middleware.Middleware
	handlers   *Handlers
}

// NewServer creates a new HTTP server
func NewServer(
	cfg *config.Config,
	logger *zap.Logger,
	mw middleware.Middleware,
	handlers *Handlers,
) *Server {
	mux := http.NewServeMux()

	s := &Server{
		logger:     logger,
		config:     cfg,
		mux:        mux,
		middleware: mw,
		handlers:   handlers,
	}

	// Register routes
	s.registerRoutes()

	// Create HTTP server with configuration
	s.server = &http.Server{
		Addr:         fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
		Handler:      s.applyMiddleware(mux),
		ReadTimeout:  cfg.Server.ReadTimeout,
		WriteTimeout: cfg.Server.WriteTimeout,
		IdleTimeout:  cfg.Server.IdleTimeout,
	}

	return s
}

// Start starts the HTTP server
func (s *Server) Start() error {
	s.logger.Info("Starting HTTP server",
		zap.String("address", s.server.Addr),
		zap.String("environment", s.config.App.Environment),
	)

	if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		return err
	}
	return nil
}

// Shutdown gracefully shuts down the HTTP server
func (s *Server) Shutdown(ctx context.Context) error {
	s.logger.Info("Shutting down HTTP server")
	return s.server.Shutdown(ctx)
}

// applyMiddleware applies global middleware to the handler
func (s *Server) applyMiddleware(handler http.Handler) http.Handler {
	// Apply middleware in reverse order (last applied is executed first)
	// TODO: Add more middleware in Phase 6

	// Logging middleware (outermost)
	handler = s.loggingMiddleware(handler)

	// CORS middleware
	handler = s.corsMiddleware(handler)

	// Recovery middleware (catches panics)
	handler = s.recoveryMiddleware(handler)

	return handler
}

// registerRoutes registers all HTTP routes
func (s *Server) registerRoutes() {
	s.logger.Info("Registering HTTP routes")

	// ===== Public Routes =====
	s.mux.HandleFunc("GET /health", s.healthCheckHandler)
	s.mux.HandleFunc("GET /version", s.versionHandler)

	// TODO: Auth routes to be implemented in Phase 7
	// s.mux.HandleFunc("POST /api/v1/auth/register", authHandler.Register)
	// s.mux.HandleFunc("POST /api/v1/auth/login", authHandler.Login)

	// ===== Protected Routes =====

	// Me / Profile routes
	s.mux.HandleFunc("GET /api/v1/me", s.handlers.GetMe.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/me", s.handlers.UpdateMe.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/me", s.handlers.DeleteMe.ServeHTTP)
	s.mux.HandleFunc("POST /api/v1/me/email/change-request", s.handlers.ChangeEmailRequest.ServeHTTP)
	s.mux.HandleFunc("POST /api/v1/me/email/change-verify", s.handlers.ChangeEmailVerify.ServeHTTP)

	// Blocked Email routes
	s.mux.HandleFunc("POST /api/v1/me/blocked-emails", s.handlers.CreateBlockedEmail.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/me/blocked-emails", s.handlers.ListBlockedEmails.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/me/blocked-emails/{email}", s.handlers.DeleteBlockedEmail.ServeHTTP)

	// Invite Email routes
	s.mux.HandleFunc("POST /api/v1/invites/send-email", s.handlers.SendInviteEmail.ServeHTTP)

	// Dashboard
	s.mux.HandleFunc("GET /api/v1/dashboard", s.handlers.GetDashboard.ServeHTTP)

	// Collections - Basic CRUD
	s.mux.HandleFunc("POST /api/v1/collections", s.handlers.CreateCollection.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/collections", s.handlers.ListUserCollections.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/collections/{id}", s.handlers.GetCollection.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/collections/{id}", s.handlers.UpdateCollection.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/collections/{id}", s.handlers.SoftDeleteCollection.ServeHTTP)

	// Collections - Hierarchical
	s.mux.HandleFunc("GET /api/v1/collections/root", s.handlers.FindRootCollections.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/collections/parent/{parent_id}", s.handlers.FindCollectionsByParent.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/collections/{id}/move", s.handlers.MoveCollection.ServeHTTP)

	// Collections - Sharing
	s.mux.HandleFunc("POST /api/v1/collections/{id}/share", s.handlers.ShareCollection.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/collections/{id}/members/{user_id}", s.handlers.RemoveMember.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/collections/shared", s.handlers.ListSharedCollections.ServeHTTP)

	// Collections - Operations
	s.mux.HandleFunc("PUT /api/v1/collections/{id}/archive", s.handlers.ArchiveCollection.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/collections/{id}/restore", s.handlers.RestoreCollection.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/collections/filtered", s.handlers.GetFilteredCollections.ServeHTTP)
	s.mux.HandleFunc("POST /api/v1/collections/sync", s.handlers.CollectionSync.ServeHTTP)

	// Files - Non-parameterized routes (no wildcards)
	s.mux.HandleFunc("POST /api/v1/files/pending", s.handlers.CreatePendingFile.ServeHTTP)
	s.mux.HandleFunc("POST /api/v1/files/delete-multiple", s.handlers.DeleteMultipleFiles.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/files/recent", s.handlers.ListRecentFiles.ServeHTTP)
	s.mux.HandleFunc("POST /api/v1/files/sync", s.handlers.FileSync.ServeHTTP)

	// Files - Parameterized routes under /file/ prefix (singular) to avoid conflicts
	s.mux.HandleFunc("POST /api/v1/file/{id}/complete", s.handlers.CompleteFileUpload.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/file/{id}/archive", s.handlers.ArchiveFile.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/file/{id}/restore", s.handlers.RestoreFile.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/file/{id}/upload-url", s.handlers.GetPresignedUploadURL.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/file/{id}/download-url", s.handlers.GetPresignedDownloadURL.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/file/{id}", s.handlers.GetFile.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/file/{id}", s.handlers.UpdateFile.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/file/{id}", s.handlers.SoftDeleteFile.ServeHTTP)

	// Files by collection - under /collection/ prefix
	s.mux.HandleFunc("GET /api/v1/collection/{collection_id}/files", s.handlers.ListFilesByCollection.ServeHTTP)

	// Tags - Basic CRUD
	s.mux.HandleFunc("POST /api/v1/tags", s.handlers.CreateTag.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/tags", s.handlers.ListTags.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/tags/{id}", s.handlers.GetTag.ServeHTTP)
	s.mux.HandleFunc("PUT /api/v1/tags/{id}", s.handlers.UpdateTag.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/tags/{id}", s.handlers.DeleteTag.ServeHTTP)

	// Tags - Assignment
	s.mux.HandleFunc("POST /api/v1/tags/{id}/assign", s.handlers.AssignTag.ServeHTTP)
	s.mux.HandleFunc("DELETE /api/v1/tags/{tagId}/entities/{entityId}", s.handlers.UnassignTag.ServeHTTP)

	// Tags - Entity lookups
	s.mux.HandleFunc("GET /api/v1/collections/{id}/tags", s.handlers.GetTagsForCollection.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/files/{id}/tags", s.handlers.GetTagsForFile.ServeHTTP)

	// Tags - Multi-tag filtering (requires tags query parameter with comma-separated UUIDs)
	s.mux.HandleFunc("GET /api/v1/tags/collections", s.handlers.ListCollectionsByTag.ServeHTTP)
	s.mux.HandleFunc("GET /api/v1/tags/files", s.handlers.ListFilesByTag.ServeHTTP)

	s.logger.Info("HTTP routes registered", zap.Int("total_routes", 58))
}

// Health check handler
func (s *Server) healthCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status":"healthy","service":"maplefile-backend"}`))
}

// Version handler
func (s *Server) versionHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	version := fmt.Sprintf(`{"version":"%s","environment":"%s"}`,
		s.config.App.Version,
		s.config.App.Environment)
	w.Write([]byte(version))
}

// Middleware implementations

func (s *Server) loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Skip logging health check requests to reduce noise
		if r.URL.Path == "/health" {
			next.ServeHTTP(w, r)
			return
		}

		start := time.Now()

		// Wrap response writer to capture status code
		wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}

		next.ServeHTTP(wrapped, r)

		duration := time.Since(start)

		s.logger.Info("HTTP request",
			zap.String("method", r.Method),
			zap.String("path", r.URL.Path),
			zap.Int("status", wrapped.statusCode),
			zap.Duration("duration", duration),
			zap.String("remote_addr", validation.MaskIP(r.RemoteAddr)),
		)
	})
}

func (s *Server) corsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get the origin from the request
		origin := r.Header.Get("Origin")

		// Build allowed origins map
		allowedOrigins := make(map[string]bool)

		// In development, always allow localhost origins
		if s.config.App.Environment == "development" {
			allowedOrigins["http://localhost:5173"] = true // Vite dev server
			allowedOrigins["http://localhost:5174"] = true // Alternative Vite port
			allowedOrigins["http://localhost:3000"] = true // Common React port
			allowedOrigins["http://127.0.0.1:5173"] = true
			allowedOrigins["http://127.0.0.1:5174"] = true
			allowedOrigins["http://127.0.0.1:3000"] = true
		}

		// Add production origins from configuration
		for _, allowedOrigin := range s.config.Security.AllowedOrigins {
			if allowedOrigin != "" {
				allowedOrigins[allowedOrigin] = true
			}
		}

		// Check if the request origin is allowed
		if allowedOrigins[origin] {
			// SECURITY FIX: Validate origin before setting CORS headers
			// CWE-942: Permissive Cross-domain Policy with Untrusted Domains
			// OWASP A05:2021: Security Misconfiguration - Secure CORS configuration

			// Prevent wildcard origin with credentials (major security risk)
			if origin == "*" {
				s.logger.Error("CRITICAL: Wildcard origin (*) cannot be used with credentials",
					zap.String("path", r.URL.Path))
				// Don't set CORS headers for wildcard - this is a misconfiguration
				next.ServeHTTP(w, r)
				return
			}

			// In production, enforce HTTPS origins for security
			if s.config.App.Environment == "production" {
				if len(origin) >= 5 && origin[:5] == "http:" {
					s.logger.Warn("Non-HTTPS origin rejected in production",
						zap.String("origin", origin),
						zap.String("path", r.URL.Path))
					// Don't set CORS headers for non-HTTPS origins in production
					next.ServeHTTP(w, r)
					return
				}
			}

			// Set CORS headers for validated origins
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

			// Only set credentials for specific, non-wildcard origins
			// This prevents credential leakage to untrusted domains
			if origin != "*" && origin != "" {
				w.Header().Set("Access-Control-Allow-Credentials", "true")
			}

			w.Header().Set("Access-Control-Max-Age", "3600") // Cache preflight for 1 hour

			s.logger.Debug("CORS headers added",
				zap.String("origin", origin),
				zap.String("path", r.URL.Path),
				zap.Bool("credentials_allowed", origin != "*"))
		} else if origin != "" {
			// Log rejected origins for debugging
			s.logger.Warn("CORS request from disallowed origin",
				zap.String("origin", origin),
				zap.String("path", r.URL.Path))
		}

		// Handle preflight requests
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}

		next.ServeHTTP(w, r)
	})
}

func (s *Server) recoveryMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				s.logger.Error("Panic recovered",
					zap.Any("error", err),
					zap.String("path", r.URL.Path),
				)
				problem := httperror.NewInternalServerError("An unexpected error occurred")
				problem.WithInstance(r.URL.Path)
				httperror.RespondWithProblem(w, problem)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

// responseWriter wraps http.ResponseWriter to capture status code
type responseWriter struct {
	http.ResponseWriter
	statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.statusCode = code
	rw.ResponseWriter.WriteHeader(code)
}
