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/store"
)

// projectRequest is the expected JSON body for creating/updating a project.
type projectRequest struct {
	Name        string `json:"name"`
	Registry    string `json:"registry"`
	Image       string `json:"image"`
	Port        int    `json:"port"`
	Healthcheck string `json:"healthcheck"`
	Env         string `json:"env"`
	Volumes     string `json:"volumes"`
}

// listProjects handles GET /api/projects.
func (s *Server) listProjects(w http.ResponseWriter, r *http.Request) {
	projects, err := s.store.GetAllProjects()
	if err != nil {
		slog.Error("failed to list projects", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}
	respondJSON(w, http.StatusOK, projects)
}

// createProject handles POST /api/projects.
func (s *Server) createProject(w http.ResponseWriter, r *http.Request) {
	var req projectRequest
	if !decodeJSON(w, r, &req) {
		return
	}

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

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

	s.eventBus.Publish(events.Event{
		Type: events.EventLog,
		Payload: events.EventLogPayload{
			Source:   "admin",
			Severity: "info",
			Message:  "project created: " + project.Name,
		},
	})

	respondJSON(w, http.StatusCreated, project)
}

// getProject handles GET /api/projects/{id}.
func (s *Server) getProject(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	project, err := s.store.GetProjectByID(id)
	if 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
	}

	// Also fetch stages for this project.
	stages, err := s.store.GetStagesByProjectID(id)
	if err != nil {
		slog.Error("failed to get stages", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

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

// updateProject handles PUT /api/projects/{id}.
func (s *Server) updateProject(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	existing, err := s.store.GetProjectByID(id)
	if 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
	}

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

	// Apply updates to existing project, preserving fields not provided.
	updated := existing
	if req.Name != "" {
		updated.Name = req.Name
	}
	if req.Image != "" {
		updated.Image = req.Image
	}
	updated.Registry = req.Registry
	updated.Port = req.Port
	updated.Healthcheck = req.Healthcheck
	if req.Env != "" {
		updated.Env = req.Env
	}
	if req.Volumes != "" {
		updated.Volumes = req.Volumes
	}

	if err := s.store.UpdateProject(updated); err != nil {
		slog.Error("failed to update project", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	s.eventBus.Publish(events.Event{
		Type: events.EventLog,
		Payload: events.EventLogPayload{
			Source:   "admin",
			Severity: "info",
			Message:  "project updated: " + updated.Name,
		},
	})

	respondJSON(w, http.StatusOK, updated)
}

// deleteProject handles DELETE /api/projects/{id}.
func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if err := s.store.DeleteProject(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "project")
			return
		}
		slog.Error("failed to delete project", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}
	s.eventBus.Publish(events.Event{
		Type: events.EventLog,
		Payload: events.EventLogPayload{
			Source:   "admin",
			Severity: "info",
			Message:  "project deleted: " + id,
		},
	})

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