package api

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"net/http"

	"github.com/go-chi/chi/v5"

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

// listInstances handles GET /api/projects/{id}/stages/{stage}/instances.
func (s *Server) listInstances(w http.ResponseWriter, r *http.Request) {
	stageID := chi.URLParam(r, "stage")

	// Verify stage exists.
	if _, err := s.store.GetStageByID(stageID); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "stage")
			return
		}
		slog.Error("failed to get stage", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

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

// deployRequest is the expected JSON body for triggering a deploy.
type deployRequest struct {
	ImageTag string `json:"image_tag"`
}

// deployInstance handles POST /api/projects/{id}/stages/{stage}/instances (trigger deploy).
func (s *Server) deployInstance(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	stageID := chi.URLParam(r, "stage")

	// Verify project exists.
	if _, err := s.store.GetProjectByID(projectID); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "project")
			return
		}
		slog.Error("failed to get project", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	// Verify stage exists.
	if _, err := s.store.GetStageByID(stageID); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "stage")
			return
		}
		slog.Error("failed to get stage", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	var req deployRequest
	if !decodeJSON(w, r, &req) {
		return
	}

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

	deployID, err := s.deployer.AsyncTriggerDeploy(r.Context(), projectID, stageID, req.ImageTag)
	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]string{
		"status":     "deploying",
		"deploy_id":  deployID,
		"project_id": projectID,
		"stage_id":   stageID,
		"image_tag":  req.ImageTag,
	})
}

// removeInstance handles DELETE /api/projects/{id}/stages/{stage}/instances/{iid}.
func (s *Server) removeInstance(w http.ResponseWriter, r *http.Request) {
	instanceID := chi.URLParam(r, "iid")

	inst, err := s.store.GetInstanceByID(instanceID)
	if err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "instance")
			return
		}
		slog.Error("failed to get instance", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	// Remove the Docker container if it has one.
	if inst.ContainerID != "" {
		if err := s.docker.RemoveContainer(r.Context(), inst.ContainerID, true); err != nil {
			slog.Error("remove container", "container_id", inst.ContainerID, "error", err)
		}
	}

	// Delete proxy route if it has one.
	if inst.ProxyRouteID != "" {
		if err := s.proxyProvider.DeleteRoute(r.Context(), inst.ProxyRouteID); err != nil {
			slog.Warn("delete proxy route on instance removal", "route_id", inst.ProxyRouteID, "error", err)
		}
	}

	// Delete instance record.
	if err := s.store.DeleteInstance(instanceID); err != nil {
		respondError(w, http.StatusInternalServerError, "failed to delete instance")
		return
	}
	respondJSON(w, http.StatusOK, map[string]string{"deleted": instanceID})
}

// stopInstance handles POST /api/projects/{id}/stages/{stage}/instances/{iid}/stop.
func (s *Server) stopInstance(w http.ResponseWriter, r *http.Request) {
	s.controlInstance(w, r, "stop")
}

// startInstance handles POST /api/projects/{id}/stages/{stage}/instances/{iid}/start.
func (s *Server) startInstance(w http.ResponseWriter, r *http.Request) {
	s.controlInstance(w, r, "start")
}

// restartInstance handles POST /api/projects/{id}/stages/{stage}/instances/{iid}/restart.
func (s *Server) restartInstance(w http.ResponseWriter, r *http.Request) {
	s.controlInstance(w, r, "restart")
}

// controlInstance performs a stop/start/restart action on an instance's container.
func (s *Server) controlInstance(w http.ResponseWriter, r *http.Request, action string) {
	instanceID := chi.URLParam(r, "iid")

	inst, err := s.store.GetInstanceByID(instanceID)
	if err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "instance")
			return
		}
		slog.Error("failed to get instance", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	if inst.ContainerID == "" {
		respondError(w, http.StatusBadRequest, "instance has no container")
		return
	}

	ctx := r.Context()
	var controlErr error
	var newStatus string

	switch action {
	case "stop":
		controlErr = s.docker.StopContainer(ctx, inst.ContainerID, 10)
		newStatus = "stopped"
	case "start":
		controlErr = s.docker.StartContainer(ctx, inst.ContainerID)
		newStatus = "running"
	case "restart":
		controlErr = s.docker.RestartContainer(ctx, inst.ContainerID, 10)
		newStatus = "running"
	default:
		respondError(w, http.StatusBadRequest, fmt.Sprintf("unknown action: %s", action))
		return
	}

	if controlErr != nil {
		slog.Error("failed to control instance", "action", action, "instance_id", instanceID, "error", controlErr)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	// Update status in store.
	if err := s.store.UpdateInstanceStatus(instanceID, newStatus); err != nil {
		slog.Error("update instance status", "instance_id", instanceID, "status", newStatus, "error", err)
	}

	// Track last_alive_at when container becomes running.
	if newStatus == "running" {
		if err := s.store.UpdateLastAliveAt(instanceID); err != nil {
			slog.Error("update last_alive_at", "instance_id", instanceID, "error", err)
		}
	}

	respondJSON(w, http.StatusOK, map[string]string{
		"instance_id": instanceID,
		"action":      action,
		"status":      newStatus,
	})
}

// DeployTriggerer is the interface for triggering deployments.
type DeployTriggerer interface {
	TriggerDeploy(ctx context.Context, projectID, stageID, imageTag string) error
	AsyncTriggerDeploy(ctx context.Context, projectID, stageID, imageTag string) (string, error)
}
