package api

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

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

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

// listStaleContainers handles GET /api/containers/stale.
func (s *Server) listStaleContainers(w http.ResponseWriter, r *http.Request) {
	if s.staleScanner == nil {
		respondError(w, http.StatusServiceUnavailable, "stale scanner not initialized")
		return
	}

	staleInstances, err := s.staleScanner.FindStaleInstances(r.Context())
	if err != nil {
		slog.Error("failed to find stale containers", "error", err)
		respondError(w, http.StatusInternalServerError, "failed to find stale containers")
		return
	}

	if staleInstances == nil {
		staleInstances = []stale.StaleInstance{}
	}
	respondJSON(w, http.StatusOK, staleInstances)
}

// cleanupStaleContainer handles POST /api/containers/stale/{id}/cleanup.
// Stops the Docker container, removes the NPM proxy, and deletes the instance from the store.
func (s *Server) cleanupStaleContainer(w http.ResponseWriter, r *http.Request) {
	instanceID := chi.URLParam(r, "id")

	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", "instance_id", instanceID, "error", err)
		respondError(w, http.StatusInternalServerError, "failed to get instance")
		return
	}

	// Don't remove instances already being cleaned up.
	if inst.Status == "removing" {
		respondError(w, http.StatusConflict, "instance is already being removed")
		return
	}

	if err := s.cleanupInstance(r, inst); err != nil {
		slog.Error("failed to cleanup instance", "instance_id", instanceID, "error", err)
		respondError(w, http.StatusInternalServerError, "failed to cleanup instance")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"cleaned": instanceID})
}

// bulkCleanupStaleContainers handles POST /api/containers/stale/cleanup.
// Cleans up all currently stale containers.
func (s *Server) bulkCleanupStaleContainers(w http.ResponseWriter, r *http.Request) {
	if s.staleScanner == nil {
		respondError(w, http.StatusServiceUnavailable, "stale scanner not initialized")
		return
	}

	staleInstances, err := s.staleScanner.FindStaleInstances(r.Context())
	if err != nil {
		slog.Error("failed to find stale containers for bulk cleanup", "error", err)
		respondError(w, http.StatusInternalServerError, "failed to find stale containers")
		return
	}

	var cleaned []string
	var failed []string

	for _, si := range staleInstances {
		if si.Instance.Status == "removing" {
			continue
		}
		if err := s.cleanupInstance(r, si.Instance); err != nil {
			slog.Error("bulk stale cleanup failed",
				"instance_id", si.Instance.ID, "error", err)
			failed = append(failed, si.Instance.ID)
			continue
		}
		cleaned = append(cleaned, si.Instance.ID)
	}

	respondJSON(w, http.StatusOK, map[string]any{
		"cleaned": cleaned,
		"failed":  failed,
	})
}

// cleanupInstance stops a Docker container, removes the NPM proxy, deletes
// the store record, and emits an event.
func (s *Server) cleanupInstance(r *http.Request, inst store.Instance) error {
	ctx := r.Context()

	// Mark as removing.
	if err := s.store.UpdateInstanceStatus(inst.ID, "removing"); err != nil {
		slog.Warn("stale cleanup: update status to removing", "instance_id", inst.ID, "error", err)
	}

	// Stop and remove Docker container.
	if inst.ContainerID != "" {
		if err := s.docker.StopContainer(ctx, inst.ContainerID, 10); err != nil {
			slog.Warn("stale cleanup: stop container", "container_id", inst.ContainerID, "error", err)
		}
		if err := s.docker.RemoveContainer(ctx, inst.ContainerID, true); err != nil {
			slog.Warn("stale cleanup: remove container", "container_id", inst.ContainerID, "error", err)
		}
	}

	// Delete proxy route if present.
	if inst.ProxyRouteID != "" {
		if err := s.proxyProvider.DeleteRoute(ctx, inst.ProxyRouteID); err != nil {
			slog.Warn("stale cleanup: delete proxy route", "route_id", inst.ProxyRouteID, "error", err)
		}
	}

	// Delete instance record.
	if err := s.store.DeleteInstance(inst.ID); err != nil {
		return err
	}

	// Emit cleanup event.
	s.emitStaleCleanupEvent(inst)

	return nil
}

// emitStaleCleanupEvent publishes an event when a stale container is cleaned up.
func (s *Server) emitStaleCleanupEvent(inst store.Instance) {
	msg := "Stale container cleaned up: " + inst.ID + " (tag: " + inst.ImageTag + ")"

	evt, err := s.store.InsertEvent(store.EventLog{
		Source:   "stale_cleanup",
		Severity: "info",
		Message:  msg,
		Metadata: `{"instance_id":"` + inst.ID + `","project_id":"` + inst.ProjectID + `","stage_id":"` + inst.StageID + `"}`,
	})
	if err != nil {
		slog.Error("stale cleanup: failed to persist event", "error", err)
		return
	}

	s.eventBus.Publish(events.Event{
		Type: events.EventLog,
		Payload: events.EventLogPayload{
			ID:        evt.ID,
			Source:    "stale_cleanup",
			Severity: "info",
			Message:  msg,
			Metadata: evt.Metadata,
			CreatedAt: evt.CreatedAt,
		},
	})
}
