package handlers

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"git.linuxforward.com/byop/byop-engine/analyzer"
	"git.linuxforward.com/byop/byop-engine/dbstore"
	"git.linuxforward.com/byop/byop-engine/models"
	"git.linuxforward.com/byop/byop-engine/services"
	"github.com/gin-gonic/gin"
	git "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/sirupsen/logrus"
)

// ComponentHandler handles component-related operations and contains integrated service logic
type ComponentHandler struct {
	store       *dbstore.SQLiteStore
	entry       *logrus.Entry
	buildSvc    *services.Builder // Service for building applications
	registryUrl string            // Default registry URL, can be configured
}

// NewComponentHandler creates a new ComponentHandler
func NewComponentHandler(store *dbstore.SQLiteStore, builderSvc *services.Builder, registryUrl string) *ComponentHandler {
	return &ComponentHandler{
		store:       store,
		entry:       logrus.WithField("component", "ComponentHandler"),
		buildSvc:    builderSvc,  // Initialize the builder service with default values
		registryUrl: registryUrl, // Set the default registry URL
	}
}

// ListComponents returns all components with optional filtering
func (h *ComponentHandler) ListComponents(c *gin.Context) {
	filter := make(map[string]interface{})
	ctx := c.Request.Context()

	// Attempt to bind query parameters, but allow empty filters
	if err := c.ShouldBindQuery(&filter); err != nil && len(filter) > 0 {
		appErr := models.NewErrValidation("invalid_query_params", nil, err)
		models.RespondWithError(c, appErr)
		return
	}

	components, err := h.store.GetAllComponents(ctx)
	if err != nil {
		appErr := models.NewErrInternalServer("failed_list_components", fmt.Errorf("Failed to list components: %w", err))
		models.RespondWithError(c, appErr)
		return
	}

	// If empty, return an empty list
	if len(components) == 0 {
		c.JSON(http.StatusOK, []models.Component{})
		return
	}

	c.JSON(http.StatusOK, components)
}

// CreateComponent creates a new component
func (h *ComponentHandler) CreateComponent(c *gin.Context) {
	var component models.Component
	ctx := c.Request.Context()

	if err := c.ShouldBindJSON(&component); err != nil {
		appErr := models.NewErrValidation("invalid_request_body", nil, err)
		models.RespondWithError(c, appErr)
		return
	}

	// Get the user ID from the context (set by auth middleware)
	userIDInterface, exists := c.Get("user_id")
	if !exists {
		appErr := models.NewErrUnauthorized("user_id_not_found", fmt.Errorf("User ID not found in context"))
		models.RespondWithError(c, appErr)
		return
	}

	// Convert user_id to uint - it might be a string from JWT
	var userID uint
	switch v := userIDInterface.(type) {
	case string:
		if parsedID, err := strconv.ParseUint(v, 10, 32); err == nil {
			userID = uint(parsedID)
		} else {
			h.entry.Warnf("Failed to parse user_id string '%s' to uint, defaulting. Error: %v", v, err)
			userID = 1
		}
	case int:
		userID = uint(v)
	case int64:
		userID = uint(v)
	case uint:
		userID = v
	default:
		h.entry.Warnf("User_id in context is of unexpected type %T, defaulting.", v)
		userID = 1
	}

	// Set the user ID on the component
	component.UserID = userID

	// Validate component data
	if validationErrors := h.validateComponentRequest(&component); len(validationErrors) > 0 {
		appErr := models.NewErrValidation("invalid_component_data", validationErrors, nil)
		models.RespondWithError(c, appErr)
		return
	}

	// Set initial status to validating
	component.Status = "validating"

	// Create the component
	err := h.store.CreateComponent(ctx, &component)
	if err != nil {
		appErr := models.NewErrInternalServer("failed_create_component", fmt.Errorf("Failed to create component: %w", err))
		models.RespondWithError(c, appErr)
		return
	}

	// The ID is automatically set by GORM after creation

	// Start async validation
	h.entry.WithField("component_id", component.ID).Info("Starting async validation for component")
	go h.validateComponent(context.Background(), &component)

	c.JSON(http.StatusCreated, component)
}

// validateComponent asynchronously validates the component's repository and Dockerfile
// validateComponent checks if repository URL is valid, branch exists, and Dockerfile is present
// if Dockerfile is not present, it starts an async generation process
func (h *ComponentHandler) validateComponent(ctx context.Context, component *models.Component) {
	h.entry.WithField("component_id", component.ID).Info("Starting validation for component")

	// Create a temporary directory for cloning and Dockerfile generation
	tempDir, err := os.MkdirTemp("", fmt.Sprintf("byop-validate-%d-*", component.ID))
	if err != nil {
		h.entry.WithField("component_id", component.ID).Errorf("Failed to create temp directory: %v", err)
		// Update component status to invalid - use background context to avoid cancellation
		if updateErr := h.store.UpdateComponentStatus(context.Background(), component.ID, "invalid", fmt.Sprintf("Failed to create temp dir: %v", err)); updateErr != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status: %v", updateErr)
		}
		return
	}
	// Change permissions of tempDir to allow access by other users (e.g., buildkitd)
	if err := os.Chmod(tempDir, 0755); err != nil {
		h.entry.Errorf("Failed to chmod tempDir %s for component %d: %v", tempDir, component.ID, err)
		if updateErr := h.store.UpdateComponentStatus(context.Background(), component.ID, "invalid", fmt.Sprintf("Failed to set permissions on temp dir: %v", err)); updateErr != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status: %v", updateErr)
		}
		// Attempt to clean up tempDir if chmod fails and we are returning early,
		// as no build job will be queued for it.
		if errRemove := os.RemoveAll(tempDir); errRemove != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Error removing temp dir %s after chmod failure: %v", tempDir, errRemove)
		}
		return
	}
	h.entry.Debugf("Set permissions to 0755 for tempDir: %s", tempDir)

	// Log the start of validation
	h.entry.WithField("component_id", component.ID).Info("Validating component repository and Dockerfile")
	if err := h.validateRepoAndBranch(ctx, *component, tempDir); err != nil {
		h.entry.WithField("component_id", component.ID).Errorf("Validation failed: %v", err)

		// Update component status to invalid - use background context to avoid cancellation
		if updateErr := h.store.UpdateComponentStatus(context.Background(), component.ID, "invalid", err.Error()); updateErr != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status: %v", updateErr)
		}
		// Attempt to clean up tempDir if validation fails and we are returning early.
		if errRemove := os.RemoveAll(tempDir); errRemove != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Error removing temp dir %s after validation failure: %v", tempDir, errRemove)
		}
		return
	}

	// If Dockerfile does not exist, start generation
	h.entry.WithField("component_id", component.ID).Info("Dockerfile not found, starting generation")
	if err := h.store.UpdateComponentStatus(context.Background(), component.ID, "generating", "Dockerfile not found, generating..."); err != nil {
		h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status to generating: %v", err)
		return
	}
	// Guess the type of Dockerfile to generate based on component type
	stack, err := analyzer.AnalyzeCode(tempDir)
	if err != nil {
		h.entry.WithField("component_id", component.ID).Errorf("Failed to analyze code for Dockerfile generation: %v", err)
		// Update component status to invalid - use background context to avoid cancellation
		if updateErr := h.store.UpdateComponentStatus(context.Background(), component.ID, "invalid", fmt.Sprintf("Code analysis failed: %v", err)); updateErr != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status: %v", updateErr)
		}
		// Attempt to clean up tempDir if analysis fails.
		if errRemove := os.RemoveAll(tempDir); errRemove != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Error removing temp dir %s after code analysis failure: %v", tempDir, errRemove)
		}
		return
	}

	dockerfileContent, err := stack.GenerateDockerfile(tempDir)
	if err != nil {
		h.entry.WithField("component_id", component.ID).Errorf("Failed to generate Dockerfile: %v", err)
		// Update component status to invalid - use background context to avoid cancellation
		if updateErr := h.store.UpdateComponentStatus(context.Background(), component.ID, "invalid", fmt.Sprintf("Dockerfile generation failed: %v", err)); updateErr != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status: %v", updateErr)
		}
		// Attempt to clean up tempDir if Dockerfile generation fails.
		if errRemove := os.RemoveAll(tempDir); errRemove != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Error removing temp dir %s after Dockerfile generation failure: %v", tempDir, errRemove)
		}
		return
	}

	// Write the generated Dockerfile to the temp directory
	dockerfilePath := filepath.Join(tempDir, "Dockerfile")
	if err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0644); err != nil {
		h.entry.WithField("component_id", component.ID).Errorf("Failed to write generated Dockerfile: %v", err)
		// Update component status to invalid - use background context to avoid cancellation
		if updateErr := h.store.UpdateComponentStatus(context.Background(), component.ID, "invalid", fmt.Sprintf("Failed to write Dockerfile: %v", err)); updateErr != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Failed to update component status: %v", updateErr)
		}
		// Attempt to clean up tempDir if writing fails.
		if errRemove := os.RemoveAll(tempDir); errRemove != nil {
			h.entry.WithField("component_id", component.ID).Errorf("Error removing temp dir %s after Dockerfile write failure: %v", tempDir, errRemove)
		}
		return
	}

	h.entry.WithField("component_id", component.ID).Info("Dockerfile generated, queueing build job.")
	// Debug: Log the first few lines of generated Dockerfile content
	lines := strings.Split(dockerfileContent, "\n")
	if len(lines) > 5 {
		lines = lines[:5]
	}
	h.entry.WithField("component_id", component.ID).Infof("Generated Dockerfile first 5 lines:\n%s", strings.Join(lines, "\n"))

	// Queue the build job with the generated Dockerfile
	h.buildSvc.QueueBuildJob(context.Background(), models.BuildRequest{
		ComponentID:       uint(component.ID),
		SourceURL:         component.Repository,
		ImageName:         fmt.Sprintf("byop-component-%d", component.ID),
		Version:           "latest",          // Default version, can be changed later
		BuildContext:      tempDir,           // Use the temp directory as the build context
		DockerfileContent: dockerfileContent, // Pass the generated Dockerfile content
		Dockerfile:        "Dockerfile",      // Standard Dockerfile name
		RegistryURL:       h.registryUrl,     // Use the configured registry URL
	})
	// Do not remove tempDir here; buildSvc is responsible for it.
}

// validateRepository validates the Git repository
func (h *ComponentHandler) validateRepoAndBranch(ctx context.Context, component models.Component, tempDir string) error {
	// Clone the repository
	h.entry.WithField("component_id", component.ID).Infof("Cloning repository %s on branch %s to %s", component.Repository, component.Branch, tempDir)
	if err := h.cloneRepository(component.Repository, component.Branch, tempDir); err != nil {
		return fmt.Errorf("failed to clone repository: %w", err)
	}

	h.entry.WithField("component_id", component.ID).Info("Repository and Dockerfile validation successful")
	return nil
}

// cloneRepository clones a Git repository to the specified directory
func (h *ComponentHandler) cloneRepository(repoURL, branch, targetDir string) error {
	// Create target directory
	if err := os.MkdirAll(targetDir, 0755); err != nil {
		return fmt.Errorf("failed to create target directory: %w", err)
	}

	// Default branch if not specified
	if branch == "" {
		branch = "main"
	}

	// Try to clone with the specified branch
	_, err := git.PlainClone(targetDir, false, &git.CloneOptions{
		URL:           repoURL,
		ReferenceName: plumbing.ReferenceName("refs/heads/" + branch),
		SingleBranch:  true,
		Depth:         1,
	})
	if err == nil {
		h.entry.WithField("repo_url", repoURL).WithField("branch", branch).Infof("Successfully cloned repository to %s", targetDir)
		return nil
	}

	// If the specified branch fails and it's "main", try "master"
	if branch == "main" {
		h.entry.WithField("repo_url", repoURL).WithField("branch", branch).Warnf("Failed to clone with 'main' branch, trying 'master': %v", err)

		// Clean up the failed clone attempt
		os.RemoveAll(targetDir)
		if err := os.MkdirAll(targetDir, 0755); err != nil {
			return fmt.Errorf("failed to recreate target directory: %w", err)
		}

		_, err := git.PlainClone(targetDir, false, &git.CloneOptions{
			URL:           repoURL,
			ReferenceName: plumbing.ReferenceName("refs/heads/master"),
			SingleBranch:  true,
			Depth:         1,
		})
		if err == nil {
			h.entry.WithField("repo_url", repoURL).WithField("branch", "master").Infof("Successfully cloned repository to %s using 'master' branch", targetDir)
			return nil
		}

		h.entry.WithField("repo_url", repoURL).Errorf("Failed to clone with both 'main' and 'master' branches")
		return fmt.Errorf("failed to clone repository %s: tried both 'main' and 'master' branches, last error: %w", repoURL, err)
	}

	h.entry.WithField("repo_url", repoURL).WithField("branch", branch).Warnf("Failed to clone repository to %s: %v", targetDir, err)
	return fmt.Errorf("failed to clone repository %s (branch %s): %w", repoURL, branch, err)
}

// GetComponent returns a specific component
func (h *ComponentHandler) GetComponent(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	component, err := h.store.GetComponentByID(ctx, id)
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	if component.ID == 0 {
		appErr := models.NewErrNotFound("component_not_found_explicit_check", fmt.Errorf("Component with ID %d not found after store call", id))
		models.RespondWithError(c, appErr)
		return
	}

	c.JSON(http.StatusOK, component)
}

// UpdateComponent updates a component
func (h *ComponentHandler) UpdateComponent(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	var updatedComponent models.Component
	if err := c.ShouldBindJSON(&updatedComponent); err != nil {
		appErr := models.NewErrValidation("invalid_request_body", nil, err)
		models.RespondWithError(c, appErr)
		return
	}

	// Ensure the ID matches the URL parameter
	updatedComponent.ID = id

	// Validate component data
	if validationErrors := h.validateComponentRequest(&updatedComponent); len(validationErrors) > 0 {
		appErr := models.NewErrValidation("invalid_component_data_update", validationErrors, nil)
		models.RespondWithError(c, appErr)
		return
	}

	if err := h.store.UpdateComponent(ctx, &updatedComponent); err != nil {
		models.RespondWithError(c, err)
		return
	}

	c.JSON(http.StatusOK, updatedComponent)
}

// DeleteComponent deletes a component
func (h *ComponentHandler) DeleteComponent(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	if err := h.store.DeleteComponent(ctx, id); err != nil {
		models.RespondWithError(c, err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Component deleted successfully"})
}

// GetComponentDeployments returns all deployments for a component
func (h *ComponentHandler) GetComponentDeployments(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	// Check if component exists
	component, err := h.store.GetComponentByID(ctx, id)
	if err != nil {
		models.RespondWithError(c, err)
		return
	}
	if component.ID == 0 {
		appErr := models.NewErrNotFound("component_not_found_for_deployments", fmt.Errorf("Component with ID %d not found when listing deployments", id))
		models.RespondWithError(c, appErr)
		return
	}

	// TODO: Retrieve deployments - this likely requires a separate repository or service
	deployments := []models.Deployment{}

	c.JSON(http.StatusOK, deployments)
}

// validateComponentRequest checks if the component data is valid and returns a map of validation errors
func (h *ComponentHandler) validateComponentRequest(component *models.Component) map[string]string {
	errors := make(map[string]string)
	if component.Name == "" {
		errors["name"] = "Component name is required"
	}
	if component.Type == "" {
		errors["type"] = "Component type is required"
	}
	if component.Repository == "" {
		errors["repository"] = "Component Git URL is required"
	}
	return errors
}
