package handlers

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

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/pkg/api"
)

// maxReplicas is the maximum number of deployment replicas allowed.
const maxReplicas = 10

// DeployRequest is the request body for POST /projects/{id}/deploy.
type DeployRequest struct {
	Component string            `json:"component,omitempty"` // Component path (e.g., "services/auth-api"), empty for single-app or all components
	Image     string            `json:"image"`               // Container image
	Domain    string            `json:"domain,omitempty"`    // Custom domain (optional)
	Port      int               `json:"port,omitempty"`      // Container port (default 8080)
	Replicas  int               `json:"replicas,omitempty"`  // Number of replicas (default 1)
	EnvVars   map[string]string `json:"env_vars,omitempty"`  // Plain environment variables
	Secrets   map[string]string `json:"secrets,omitempty"`   // Secret environment variables
}

// DeployResponse is the response for POST /projects/{id}/deploy.
type DeployResponse struct {
	ProjectName   string `json:"project_name"`
	ComponentPath string `json:"component_path,omitempty"` // Component path if deploying a component
	Image         string `json:"image"`
	Domain        string `json:"domain"`
	URL           string `json:"url"`
	Status        string `json:"status"`
}

// Deploy deploys a project.
// POST /projects/{id}/deploy
func (h *InfrastructureHandler) Deploy(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
	defer cancel()

	// Validate project ID
	if err := validateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.deployer == nil {
		api.WriteInternalError(w, r, "deployer not configured")
		return
	}

	var req DeployRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	if req.Image == "" {
		api.WriteBadRequest(w, r, "image is required")
		return
	}

	// Build domain - for components, include component name in subdomain
	deployDomain := req.Domain
	if deployDomain == "" {
		if req.Component != "" {
			// For components, build domain like "component-project.domain.com"
			// Extract component name from path (e.g., "services/auth-api" -> "auth-api")
			componentName := extractComponentName(req.Component)
			deployDomain = componentName + "-" + projectID + "." + h.defaultDomain
		} else {
			deployDomain = projectID + "." + h.defaultDomain
		}
	}

	// Create DNS record if DNS provider is configured
	dnsName := projectID
	if req.Component != "" {
		dnsName = extractComponentName(req.Component) + "-" + projectID
	}
	if h.dns != nil && h.clusterIP != "" {
		_, err := h.dns.CreateRecord(ctx, domain.DNSRecord{
			Type:    "A",
			Name:    dnsName,
			Content: h.clusterIP,
			TTL:     1,
			Proxied: false,
		})
		if err != nil {
			// Check if this is a "record already exists" error (not a real failure)
			// Cloudflare returns specific error codes we could check, but for now
			// we log and continue - the record might already exist from a previous deploy
			// TODO: Add proper duplicate detection once we have structured errors from adapter
			_ = err // acknowledge error - may be duplicate record which is acceptable
		}
	}

	// Deploy
	spec := domain.DeploySpec{
		ProjectName:   projectID,
		ComponentPath: req.Component,
		Image:         req.Image,
		Domain:        deployDomain,
		Port:          req.Port,
		Replicas:      req.Replicas,
		EnvVars:       req.EnvVars,
		Secrets:       req.Secrets,
	}

	if err := h.deployer.Deploy(ctx, spec); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to deploy: %v", err))
		return
	}

	api.WriteCreated(w, r, DeployResponse{
		ProjectName:   projectID,
		ComponentPath: req.Component,
		Image:         req.Image,
		Domain:        deployDomain,
		URL:           "https://" + deployDomain,
		Status:        "deploying",
	})
}

// GetDeployStatus returns the deployment status for a project.
// GET /projects/{id}/deploy/status
func (h *InfrastructureHandler) GetDeployStatus(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

	if h.deployer == nil {
		api.WriteInternalError(w, r, "deployer not configured")
		return
	}

	status, err := h.deployer.GetStatus(ctx, projectID)
	if err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to get status: %v", err))
		return
	}

	if status == nil {
		api.WriteNotFound(w, r, fmt.Sprintf("no deployment found for project: %s", projectID))
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"project_name":   status.ProjectName,
		"image":          status.Image,
		"replicas":       status.Replicas,
		"ready_replicas": status.ReadyReplicas,
		"url":            status.URL,
		"status":         status.Status,
		"created_at":     status.CreatedAt,
		"updated_at":     status.UpdatedAt,
	})
}

// Undeploy removes the deployment for a project.
// DELETE /projects/{id}/deploy
func (h *InfrastructureHandler) Undeploy(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	if h.deployer == nil {
		api.WriteInternalError(w, r, "deployer not configured")
		return
	}

	if err := h.deployer.UndeployAll(ctx, projectID); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to undeploy: %v", err))
		return
	}

	// Remove DNS record if DNS provider is configured
	if h.dns != nil {
		_ = h.dns.DeleteRecordByName(ctx, "A", projectID)
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":  "undeployed",
		"project": projectID,
	})
}

// RestartDeploy restarts the deployment for a project.
// POST /projects/{id}/deploy/restart
func (h *InfrastructureHandler) RestartDeploy(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	if h.deployer == nil {
		api.WriteInternalError(w, r, "deployer not configured")
		return
	}

	if err := h.deployer.Restart(ctx, projectID); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to restart: %v", err))
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":  "restarting",
		"project": projectID,
	})
}

// ScaleRequest is the request body for POST /projects/{id}/deploy/scale.
type ScaleRequest struct {
	Replicas int `json:"replicas"`
}

// ScaleDeploy scales the deployment for a project.
// POST /projects/{id}/deploy/scale
func (h *InfrastructureHandler) ScaleDeploy(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	if h.deployer == nil {
		api.WriteInternalError(w, r, "deployer not configured")
		return
	}

	var req ScaleRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	if req.Replicas < 0 || req.Replicas > maxReplicas {
		api.WriteBadRequest(w, r, fmt.Sprintf("replicas must be between 0 and %d", maxReplicas))
		return
	}

	if err := h.deployer.Scale(ctx, projectID, req.Replicas); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to scale: %v", err))
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"status":   "scaled",
		"project":  projectID,
		"replicas": req.Replicas,
	})
}

// GetDeployLogs returns recent logs from the deployment.
// GET /projects/{id}/deploy/logs
func (h *InfrastructureHandler) GetDeployLogs(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	if h.deployer == nil {
		api.WriteInternalError(w, r, "deployer not configured")
		return
	}

	// Default to 100 lines
	tailLines := 100

	logs, err := h.deployer.GetLogs(ctx, projectID, tailLines)
	if err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to get logs: %v", err))
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"project": projectID,
		"logs":    logs,
	})
}

// extractComponentName extracts the component name from a path like "services/auth-api".
// Returns the last segment (e.g., "auth-api").
func extractComponentName(componentPath string) string {
	if componentPath == "" {
		return ""
	}
	// Split by "/" and return last segment
	parts := make([]string, 0)
	current := ""
	for _, c := range componentPath {
		if c == '/' {
			if current != "" {
				parts = append(parts, current)
				current = ""
			}
		} else {
			current += string(c)
		}
	}
	if current != "" {
		parts = append(parts, current)
	}
	if len(parts) == 0 {
		return componentPath
	}
	return parts[len(parts)-1]
}
