package deployments

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/DeBrosOfficial/network/pkg/deployments"
	"github.com/DeBrosOfficial/network/pkg/deployments/process"
	"github.com/DeBrosOfficial/network/pkg/ipfs"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// GoHandler handles Go backend deployments
type GoHandler struct {
	service        *DeploymentService
	processManager *process.Manager
	ipfsClient     ipfs.IPFSClient
	logger         *zap.Logger
	baseDeployPath string
}

// NewGoHandler creates a new Go deployment handler
func NewGoHandler(
	service *DeploymentService,
	processManager *process.Manager,
	ipfsClient ipfs.IPFSClient,
	logger *zap.Logger,
	baseDeployPath string,
) *GoHandler {
	if baseDeployPath == "" {
		baseDeployPath = filepath.Join(os.Getenv("HOME"), ".orama", "deployments")
	}
	return &GoHandler{
		service:        service,
		processManager: processManager,
		ipfsClient:     ipfsClient,
		logger:         logger,
		baseDeployPath: baseDeployPath,
	}
}

// HandleUpload handles Go backend deployment upload
func (h *GoHandler) HandleUpload(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	namespace := getNamespaceFromContext(ctx)
	if namespace == "" {
		http.Error(w, "Namespace not found in context", http.StatusUnauthorized)
		return
	}

	// Parse multipart form (100MB max for Go binaries)
	if err := r.ParseMultipartForm(100 << 20); err != nil {
		http.Error(w, "Failed to parse form", http.StatusBadRequest)
		return
	}

	// Get metadata
	name := r.FormValue("name")
	subdomain := r.FormValue("subdomain")
	healthCheckPath := r.FormValue("health_check_path")

	if name == "" {
		http.Error(w, "Deployment name is required", http.StatusBadRequest)
		return
	}

	if healthCheckPath == "" {
		healthCheckPath = "/health"
	}

	// Parse environment variables (form fields starting with "env_")
	envVars := make(map[string]string)
	for key, values := range r.MultipartForm.Value {
		if strings.HasPrefix(key, "env_") && len(values) > 0 {
			envName := strings.TrimPrefix(key, "env_")
			envVars[envName] = values[0]
		}
	}

	// Get tarball file
	file, header, err := r.FormFile("tarball")
	if err != nil {
		http.Error(w, "Tarball file is required", http.StatusBadRequest)
		return
	}
	defer file.Close()

	h.logger.Info("Deploying Go backend",
		zap.String("namespace", namespace),
		zap.String("name", name),
		zap.String("filename", header.Filename),
		zap.Int64("size", header.Size),
	)

	// Upload to IPFS for versioning
	addResp, err := h.ipfsClient.Add(ctx, file, header.Filename)
	if err != nil {
		h.logger.Error("Failed to upload to IPFS", zap.Error(err))
		http.Error(w, "Failed to upload content", http.StatusInternalServerError)
		return
	}

	cid := addResp.Cid

	// Deploy the Go backend
	deployment, err := h.deploy(ctx, namespace, name, subdomain, cid, healthCheckPath, envVars)
	if err != nil {
		h.logger.Error("Failed to deploy Go backend", zap.Error(err))
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Create DNS records (use background context since HTTP context will be cancelled)
	go h.service.CreateDNSRecords(context.Background(), deployment)

	// Build response
	urls := h.service.BuildDeploymentURLs(deployment)

	resp := map[string]interface{}{
		"deployment_id": deployment.ID,
		"name":          deployment.Name,
		"namespace":     deployment.Namespace,
		"status":        deployment.Status,
		"type":          deployment.Type,
		"content_cid":   deployment.ContentCID,
		"urls":          urls,
		"version":       deployment.Version,
		"port":          deployment.Port,
		"created_at":    deployment.CreatedAt,
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(resp)
}

// deploy deploys a Go backend
func (h *GoHandler) deploy(ctx context.Context, namespace, name, subdomain, cid, healthCheckPath string, envVars map[string]string) (*deployments.Deployment, error) {
	// Create deployment directory
	deployPath := filepath.Join(h.baseDeployPath, namespace, name)
	if err := os.MkdirAll(deployPath, 0755); err != nil {
		return nil, fmt.Errorf("failed to create deployment directory: %w", err)
	}

	// Download and extract from IPFS
	if err := h.extractFromIPFS(ctx, cid, deployPath); err != nil {
		return nil, fmt.Errorf("failed to extract deployment: %w", err)
	}

	// Find the executable binary
	binaryPath, err := h.findBinary(deployPath)
	if err != nil {
		return nil, fmt.Errorf("failed to find binary: %w", err)
	}

	// Ensure binary is executable
	if err := os.Chmod(binaryPath, 0755); err != nil {
		return nil, fmt.Errorf("failed to make binary executable: %w", err)
	}

	h.logger.Info("Found Go binary",
		zap.String("path", binaryPath),
		zap.String("deployment", name),
	)

	// Create deployment record
	deployment := &deployments.Deployment{
		ID:                  uuid.New().String(),
		Namespace:           namespace,
		Name:                name,
		Type:                deployments.DeploymentTypeGoBackend,
		Version:             1,
		Status:              deployments.DeploymentStatusDeploying,
		ContentCID:          cid,
		Subdomain:           subdomain,
		Environment:         envVars,
		MemoryLimitMB:       256,
		CPULimitPercent:     100,
		HealthCheckPath:     healthCheckPath,
		HealthCheckInterval: 30,
		RestartPolicy:       deployments.RestartPolicyAlways,
		MaxRestartCount:     10,
		CreatedAt:           time.Now(),
		UpdatedAt:           time.Now(),
		DeployedBy:          namespace,
	}

	// Save deployment (assigns port)
	if err := h.service.CreateDeployment(ctx, deployment); err != nil {
		return nil, err
	}

	// Start the process
	if err := h.processManager.Start(ctx, deployment, deployPath); err != nil {
		deployment.Status = deployments.DeploymentStatusFailed
		h.service.UpdateDeploymentStatus(ctx, deployment.ID, deployments.DeploymentStatusFailed)
		return deployment, fmt.Errorf("failed to start process: %w", err)
	}

	// Wait for healthy
	if err := h.processManager.WaitForHealthy(ctx, deployment, 60*time.Second); err != nil {
		h.logger.Warn("Deployment did not become healthy", zap.Error(err))
		// Don't fail - the service might still be starting
	}

	deployment.Status = deployments.DeploymentStatusActive
	h.service.UpdateDeploymentStatus(ctx, deployment.ID, deployments.DeploymentStatusActive)

	return deployment, nil
}

// extractFromIPFS extracts a tarball from IPFS to a directory
func (h *GoHandler) extractFromIPFS(ctx context.Context, cid, destPath string) error {
	// Get tarball from IPFS
	reader, err := h.ipfsClient.Get(ctx, "/ipfs/"+cid, "")
	if err != nil {
		return err
	}
	defer reader.Close()

	// Create temporary file
	tmpFile, err := os.CreateTemp("", "go-deploy-*.tar.gz")
	if err != nil {
		return err
	}
	defer os.Remove(tmpFile.Name())
	defer tmpFile.Close()

	// Copy to temp file
	if _, err := io.Copy(tmpFile, reader); err != nil {
		return err
	}

	tmpFile.Close()

	// Extract tarball
	cmd := exec.Command("tar", "-xzf", tmpFile.Name(), "-C", destPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		h.logger.Error("Failed to extract tarball",
			zap.String("output", string(output)),
			zap.Error(err),
		)
		return fmt.Errorf("failed to extract tarball: %w", err)
	}

	return nil
}

// findBinary finds the Go binary in the deployment directory
func (h *GoHandler) findBinary(deployPath string) (string, error) {
	// First, look for a binary named "app" (conventional)
	appPath := filepath.Join(deployPath, "app")
	if info, err := os.Stat(appPath); err == nil && !info.IsDir() {
		return appPath, nil
	}

	// Look for any executable in the directory
	entries, err := os.ReadDir(deployPath)
	if err != nil {
		return "", fmt.Errorf("failed to read deployment directory: %w", err)
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		filePath := filepath.Join(deployPath, entry.Name())
		info, err := entry.Info()
		if err != nil {
			continue
		}

		// Check if it's executable
		if info.Mode()&0111 != 0 {
			// Skip common non-binary files
			ext := strings.ToLower(filepath.Ext(entry.Name()))
			if ext == ".sh" || ext == ".txt" || ext == ".md" || ext == ".json" || ext == ".yaml" || ext == ".yml" {
				continue
			}

			// Check if it's an ELF binary (Linux executable)
			if h.isELFBinary(filePath) {
				return filePath, nil
			}
		}
	}

	return "", fmt.Errorf("no executable binary found in deployment. Expected 'app' binary or ELF executable")
}

// isELFBinary checks if a file is an ELF binary
func (h *GoHandler) isELFBinary(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()

	// Read first 4 bytes (ELF magic number)
	magic := make([]byte, 4)
	if _, err := f.Read(magic); err != nil {
		return false
	}

	// ELF magic: 0x7f 'E' 'L' 'F'
	return magic[0] == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F'
}
