package api

import (
	"log/slog"
	"net/http"
	"strconv"
	"strings"

	"github.com/alexei/docker-watcher/internal/docker"
	"github.com/alexei/docker-watcher/internal/store"
)

// listDeploys handles GET /api/deploys.
func (s *Server) listDeploys(w http.ResponseWriter, r *http.Request) {
	limitStr := r.URL.Query().Get("limit")
	limit := 50
	if limitStr != "" {
		if parsed, err := strconv.Atoi(limitStr); err == nil && parsed > 0 {
			limit = parsed
		}
	}

	offsetStr := r.URL.Query().Get("offset")
	offset := 0
	if offsetStr != "" {
		if parsed, err := strconv.Atoi(offsetStr); err == nil && parsed >= 0 {
			offset = parsed
		}
	}

	projectID := r.URL.Query().Get("project_id")
	stageID := r.URL.Query().Get("stage_id")

	deploys, err := s.store.GetDeploys(projectID, stageID, limit, offset)
	if err != nil {
		slog.Error("failed to list deploys", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}
	respondJSON(w, http.StatusOK, deploys)
}

// NOTE: getDeployLogs has been replaced by streamDeployLogs in sse.go.
// The new handler supports both SSE streaming and JSON fallback via Accept header.

// inspectRequest is the expected JSON body for POST /api/deploy/inspect.
type inspectRequest struct {
	Image string `json:"image"`
}

// inspectResponse is the response body for POST /api/deploy/inspect.
type inspectResponse struct {
	Image       string `json:"image"`
	Port        int    `json:"port"`
	Healthcheck string `json:"healthcheck"`
}

// inspectImage handles POST /api/deploy/inspect.
// Pulls the image and inspects it for EXPOSE ports and healthcheck config.
func (s *Server) inspectImage(w http.ResponseWriter, r *http.Request) {
	var req inspectRequest
	if !decodeJSON(w, r, &req) {
		return
	}

	if req.Image == "" {
		respondError(w, http.StatusBadRequest, "image is required")
		return
	}

	ctx := r.Context()

	// Pull the image first so it's available locally for inspection.
	// Split image:tag for the pull call.
	imageRef, tag := splitImageTag(req.Image)
	if err := s.docker.PullImage(ctx, imageRef, tag, ""); err != nil {
		slog.Warn("pull image for inspect", "image", req.Image, "error", err)
		// Try to inspect anyway in case the image is already local.
	}

	info, err := s.docker.InspectImage(ctx, req.Image)
	if err != nil {
		slog.Error("failed to inspect image", "image", req.Image, "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	port := docker.ExtractPort(info.ExposedPorts)

	respondJSON(w, http.StatusOK, inspectResponse{
		Image:       req.Image,
		Port:        port,
		Healthcheck: info.Healthcheck,
	})
}

// quickDeployRequest is the expected JSON body for POST /api/deploy/quick.
type quickDeployRequest struct {
	Name     string `json:"name"`
	Image    string `json:"image"`
	Tag      string `json:"tag"`
	Registry string `json:"registry"`
	Port     int    `json:"port"`
	Force    bool   `json:"force"` // skip duplicate check
}

// quickDeploy handles POST /api/deploy/quick.
// Creates a project, a default stage, and triggers a deploy in one call.
func (s *Server) quickDeploy(w http.ResponseWriter, r *http.Request) {
	var req quickDeployRequest
	if !decodeJSON(w, r, &req) {
		return
	}

	if req.Image == "" {
		respondError(w, http.StatusBadRequest, "image is required")
		return
	}

	// Split tag from image if the image URL contains one (e.g., "registry/app:v1").
	if req.Tag == "" {
		imageRef, tag := splitImageTag(req.Image)
		if tag != "" {
			req.Image = imageRef
			req.Tag = tag
		} else {
			req.Tag = "latest"
		}
	}

	if req.Name == "" {
		// Derive name from image (without tag).
		parts := strings.Split(req.Image, "/")
		req.Name = parts[len(parts)-1]
	}

	// Check for existing projects with the same image.
	if !req.Force {
		existing, err := s.store.GetProjectsByImage(req.Image)
		if err != nil {
			slog.Error("failed to check existing projects", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
			return
		}
		if len(existing) > 0 {
			respondJSON(w, http.StatusConflict, map[string]any{
				"message":           "A project with this image already exists",
				"existing_projects": existing,
			})
			return
		}
	}

	// Create project.
	project, err := s.store.CreateProject(store.Project{
		Name:     req.Name,
		Image:    req.Image,
		Registry: req.Registry,
		Port:     req.Port,
		Env:      "{}",
		Volumes:  "{}",
	})
	if err != nil {
		slog.Error("failed to create project", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	// Create default stage.
	stage, err := s.store.CreateStage(store.Stage{
		ProjectID:    project.ID,
		Name:         "dev",
		TagPattern:   "*",
		AutoDeploy:   true,
		MaxInstances: 1,
	})
	if err != nil {
		slog.Error("failed to create stage", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	// Trigger deploy asynchronously.
	deployID, err := s.deployer.AsyncTriggerDeploy(r.Context(), project.ID, stage.ID, req.Tag)
	if err != nil {
		slog.Error("failed to trigger deploy", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	respondJSON(w, http.StatusAccepted, map[string]any{
		"project":   project,
		"stage":     stage,
		"tag":       req.Tag,
		"deploy_id": deployID,
		"status":  "deploying",
	})
}

// splitImageTag splits "image:tag" into image and tag parts.
// Returns the full string and empty tag if no colon separator is found.
func splitImageTag(ref string) (string, string) {
	if idx := strings.LastIndex(ref, ":"); idx != -1 {
		afterColon := ref[idx+1:]
		if !strings.Contains(afterColon, "/") {
			return ref[:idx], afterColon
		}
	}
	return ref, ""
}

