package site

import (
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config/constants"
	sitedto "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/dto/site"
	siteservice "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/service/site"
	siteusecase "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/usecase/site"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/dns"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/httpresponse"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/httpvalidation"
)

// CreateHandler handles site creation HTTP requests
type CreateHandler struct {
	service siteservice.CreateSiteService
	config  *config.Config
	logger  *zap.Logger
}

// ProvideCreateHandler creates a new CreateHandler
func ProvideCreateHandler(service siteservice.CreateSiteService, cfg *config.Config, logger *zap.Logger) *CreateHandler {
	return &CreateHandler{
		service: service,
		config:  cfg,
		logger:  logger,
	}
}

// Handle handles the HTTP request for creating a site
// Requires JWT authentication and tenant context
func (h *CreateHandler) Handle(w http.ResponseWriter, r *http.Request) {
	// Get tenant ID from context (populated by TenantMiddleware)
	tenantIDStr, ok := r.Context().Value(constants.ContextKeyTenantID).(string)
	if !ok {
		h.logger.Error("tenant ID not found in context")
		httperror.ProblemUnauthorized(w, "tenant context required")
		return
	}

	tenantID, err := gocql.ParseUUID(tenantIDStr)
	if err != nil {
		h.logger.Error("invalid tenant ID format", zap.Error(err))
		httperror.ProblemBadRequest(w, "invalid tenant ID")
		return
	}

	// CWE-436: Validate Content-Type before parsing
	if err := httpvalidation.ValidateJSONContentType(r); err != nil {
		httperror.ProblemBadRequest(w, err.Error())
		return
	}

	// Parse request body
	var req sitedto.CreateRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httperror.ProblemBadRequest(w, "invalid request body")
		return
	}

	// CWE-20: Comprehensive input validation
	if err := req.Validate(); err != nil {
		h.logger.Warn("site creation request validation failed", zap.Error(err))

		// Check if it's a structured validation error (RFC 9457 format)
		if validationErr, ok := err.(*sitedto.ValidationErrors); ok {
			httperror.ValidationError(w, validationErr.Errors, "One or more validation errors occurred")
			return
		}

		// Fallback for non-structured errors
		httperror.ProblemBadRequest(w, err.Error())
		return
	}

	// Extract domain from site URL
	parsedURL, err := url.Parse(req.SiteURL)
	if err != nil {
		h.logger.Warn("failed to parse site URL", zap.Error(err), zap.String("site_url", req.SiteURL))
		httperror.ValidationError(w, map[string][]string{
			"site_url": {"Invalid URL format. Please provide a valid URL (e.g., https://example.com)."},
		}, "One or more validation errors occurred")
		return
	}

	domain := parsedURL.Hostname()
	if domain == "" {
		h.logger.Warn("could not extract domain from site URL", zap.String("site_url", req.SiteURL))
		httperror.ValidationError(w, map[string][]string{
			"site_url": {"Could not extract domain from URL. Please provide a valid URL with a hostname."},
		}, "One or more validation errors occurred")
		return
	}

	// Determine test mode based on environment
	testMode := h.config.App.IsTestMode()

	h.logger.Info("creating site",
		zap.String("domain", domain),
		zap.String("site_url", req.SiteURL),
		zap.String("environment", h.config.App.Environment),
		zap.Bool("test_mode", testMode))

	// Map DTO to use case input
	input := &siteusecase.CreateSiteInput{
		Domain:   domain,
		SiteURL:  req.SiteURL,
		TestMode: testMode,
	}

	// Call service
	output, err := h.service.CreateSite(r.Context(), tenantID, input)
	if err != nil {
		h.logger.Error("failed to create site",
			zap.Error(err),
			zap.String("domain", domain),
			zap.String("site_url", req.SiteURL),
			zap.String("tenant_id", tenantID.String()))

		// Check for domain already exists error
		if err.Error() == "domain already exists" {
			httperror.ProblemConflict(w, "This domain is already registered. Each domain can only be registered once.")
			return
		}

		httperror.ProblemInternalServerError(w, "Failed to create site. Please try again later.")
		return
	}

	// Map to response DTO
	response := sitedto.CreateResponse{
		ID:                       output.ID,
		Domain:                   output.Domain,
		SiteURL:                  output.SiteURL,
		APIKey:                   output.APIKey, // Only shown once!
		Status:                   output.Status,
		VerificationToken:        output.VerificationToken,
		SearchIndexName:          output.SearchIndexName,
		VerificationInstructions: dns.GetVerificationInstructions(output.Domain, output.VerificationToken),
	}

	h.logger.Info("site created successfully",
		zap.String("site_id", output.ID),
		zap.String("domain", output.Domain),
		zap.String("tenant_id", tenantID.String()))

	// Write response with pretty JSON
	httpresponse.Created(w, response)
}
