package api

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

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

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

// registryRequest is the expected JSON body for creating/updating a registry.
type registryRequest struct {
	Name  string `json:"name"`
	URL   string `json:"url"`
	Type  string `json:"type"`
	Token string `json:"token"`
	Owner string `json:"owner"`
}

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

	// Strip tokens from response for security.
	type safeRegistry struct {
		ID        string `json:"id"`
		Name      string `json:"name"`
		URL       string `json:"url"`
		Type      string `json:"type"`
		HasToken  bool   `json:"has_token"`
		Owner     string `json:"owner"`
		CreatedAt string `json:"created_at"`
		UpdatedAt string `json:"updated_at"`
	}

	safe := make([]safeRegistry, len(registries))
	for i, reg := range registries {
		safe[i] = safeRegistry{
			ID:        reg.ID,
			Name:      reg.Name,
			URL:       reg.URL,
			Type:      reg.Type,
			HasToken:  reg.Token != "",
			Owner:     reg.Owner,
			CreatedAt: reg.CreatedAt,
			UpdatedAt: reg.UpdatedAt,
		}
	}
	respondJSON(w, http.StatusOK, safe)
}

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

	if req.Name == "" {
		respondError(w, http.StatusBadRequest, "name is required")
		return
	}
	if req.URL == "" {
		respondError(w, http.StatusBadRequest, "url is required")
		return
	}
	if req.Type == "" {
		req.Type = "generic"
	}

	// Encrypt the token if provided.
	encToken, err := crypto.EncryptIfNotEmpty(s.encKey, req.Token)
	if err != nil {
		slog.Error("failed to encrypt token", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	reg, err := s.store.CreateRegistry(store.Registry{
		Name:  req.Name,
		URL:   req.URL,
		Type:  req.Type,
		Token: encToken,
		Owner: req.Owner,
	})
	if err != nil {
		slog.Error("failed to create registry", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}

	respondJSON(w, http.StatusCreated, map[string]string{
		"id":   reg.ID,
		"name": reg.Name,
	})
}

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

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

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

	updated := existing
	if req.Name != "" {
		updated.Name = req.Name
	}
	if req.URL != "" {
		updated.URL = req.URL
	}
	if req.Type != "" {
		updated.Type = req.Type
	}
	// Owner can be set to empty string intentionally, so always update it.
	updated.Owner = req.Owner

	// Only re-encrypt if a new token is provided.
	if req.Token != "" {
		encToken, err := crypto.EncryptIfNotEmpty(s.encKey, req.Token)
		if err != nil {
			slog.Error("failed to encrypt token", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
			return
		}
		updated.Token = encToken
	}

	if err := s.store.UpdateRegistry(updated); err != nil {
		slog.Error("failed to update registry", "error", err)
		respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}
	respondJSON(w, http.StatusOK, map[string]string{
		"id":   updated.ID,
		"name": updated.Name,
	})
}

// deleteRegistry handles DELETE /api/registries/{id}.
func (s *Server) deleteRegistry(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if err := s.store.DeleteRegistry(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "registry")
			return
		}
		slog.Error("failed to delete registry", "error", err)
			respondError(w, http.StatusInternalServerError, "internal server error")
		return
	}
	respondJSON(w, http.StatusOK, map[string]string{"deleted": id})
}

// testRegistryRequest is the expected JSON body for POST /api/registries/{id}/test.
type testRegistryRequest struct {
	Image string `json:"image"`
}

// testRegistry handles POST /api/registries/{id}/test.
// Creates a temp registry client and attempts to list tags.
func (s *Server) testRegistry(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

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

	// Body is optional — if no image provided, just test connectivity.
	var req testRegistryRequest
	if r.Body != nil && r.ContentLength > 0 {
		if !decodeJSON(w, r, &req) {
			return
		}
	}

	// Decrypt the token.
	token := reg.Token
	if token != "" {
		decrypted, err := crypto.Decrypt(s.encKey, token)
		if err != nil {
			respondError(w, http.StatusInternalServerError, "failed to decrypt registry token")
			return
		}
		token = decrypted
	}

	client, err := registry.NewClient(reg.Type, reg.URL, token)
	if err != nil {
		respondError(w, http.StatusBadRequest, "unsupported registry type: "+reg.Type)
		return
	}

	// If no image provided, just verify we can create a client (basic connectivity test).
	if req.Image == "" {
		respondJSON(w, http.StatusOK, map[string]any{
			"message": "registry client created successfully",
		})
		return
	}

	tags, err := client.ListTags(r.Context(), req.Image)
	if err != nil {
		respondError(w, http.StatusBadGateway, "registry test failed: "+err.Error())
		return
	}

	respondJSON(w, http.StatusOK, map[string]any{
		"success": true,
		"tags":    len(tags),
	})
}

// listRegistryTags handles GET /api/registries/{id}/tags/{image}.
func (s *Server) listRegistryTags(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	image := chi.URLParam(r, "*")

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

	// Decrypt the token.
	token := reg.Token
	if token != "" {
		decrypted, err := crypto.Decrypt(s.encKey, token)
		if err != nil {
			respondError(w, http.StatusInternalServerError, "failed to decrypt registry token")
			return
		}
		token = decrypted
	}

	client, err := registry.NewClient(reg.Type, reg.URL, token)
	if err != nil {
		respondError(w, http.StatusBadRequest, "unsupported registry type: "+reg.Type)
		return
	}

	tags, err := client.ListTags(r.Context(), image)
	if err != nil {
		respondError(w, http.StatusBadGateway, "failed to list tags: "+err.Error())
		return
	}

	respondJSON(w, http.StatusOK, tags)
}

// listRegistryImages handles GET /api/registries/{id}/images.
// Returns all container images available in the registry for the configured owner.
func (s *Server) listRegistryImages(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

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

	if reg.Owner == "" {
		respondError(w, http.StatusBadRequest, "registry has no owner configured; set the owner in registry settings")
		return
	}

	// Decrypt the token.
	token := reg.Token
	if token != "" {
		decrypted, err := crypto.Decrypt(s.encKey, token)
		if err != nil {
			respondError(w, http.StatusInternalServerError, "failed to decrypt registry token")
			return
		}
		token = decrypted
	}

	client, err := registry.NewClient(reg.Type, reg.URL, token)
	if err != nil {
		respondError(w, http.StatusBadRequest, "unsupported registry type: "+reg.Type)
		return
	}

	// Support comma-separated owners (e.g., "alexei,team-org,other-user").
	owners := strings.Split(reg.Owner, ",")
	var allImages []registry.RegistryImage
	for _, owner := range owners {
		owner = strings.TrimSpace(owner)
		if owner == "" {
			continue
		}
		images, err := client.ListImages(r.Context(), owner)
		if err != nil {
			slog.Warn("failed to list images for owner", "owner", owner, "error", err)
			continue
		}
		allImages = append(allImages, images...)
	}
	if allImages == nil {
		allImages = []registry.RegistryImage{}
	}

	respondJSON(w, http.StatusOK, allImages)
}
