package handlers

import (
	"context"
	"errors"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/auth"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/internal/validate"
	"github.com/orchard9/rdev/pkg/api"
)

// ProjectAccessHandler handles project-centric key access management endpoints.
type ProjectAccessHandler struct {
	authService *auth.Service
}

// NewProjectAccessHandler creates a new project access handler.
func NewProjectAccessHandler(authService *auth.Service) *ProjectAccessHandler {
	return &ProjectAccessHandler{authService: authService}
}

// Mount registers the project access routes.
func (h *ProjectAccessHandler) Mount(r api.Router) {
	r.Route("/projects/{id}/access", func(r chi.Router) {
		r.With(auth.RequireScope(auth.ScopeAdmin)).Get("/", h.List)
		r.With(auth.RequireScope(auth.ScopeAdmin)).Post("/", h.Grant)
		r.With(auth.RequireScope(auth.ScopeAdmin)).Delete("/{keyId}", h.Revoke)
	})
}

// ProjectAccessResponse is the response for GET /projects/{id}/access.
type ProjectAccessResponse struct {
	ProjectID         string        `json:"project_id"`
	Keys              []KeyResponse `json:"keys"`
	UnrestrictedCount int           `json:"unrestricted_keys"`
}

// GrantAccessRequest is the JSON body for POST /projects/{id}/access.
type GrantAccessRequest struct {
	KeyID string `json:"key_id"`
}

// List returns all keys with access to a project.
// GET /projects/{id}/access
func (h *ProjectAccessHandler) List(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutFastLookup)
	defer cancel()

	// Keys explicitly granted this project
	keys, err := h.authService.ListByProjectID(ctx, domain.ProjectID(projectID))
	if err != nil {
		api.WriteInternalError(w, r, "failed to list project access")
		return
	}

	// Count unrestricted keys (nil project_ids) from all keys
	allKeys, err := h.authService.List(ctx)
	if err != nil {
		api.WriteInternalError(w, r, "failed to list keys")
		return
	}
	unrestrictedCount := 0
	for _, k := range allKeys {
		if k.ProjectIDs == nil && k.IsActive() {
			unrestrictedCount++
		}
	}

	keyResponses := make([]KeyResponse, len(keys))
	for i, k := range keys {
		keyResponses[i] = apiKeyToResponse(k)
	}

	api.WriteSuccess(w, r, ProjectAccessResponse{
		ProjectID:         projectID,
		Keys:              keyResponses,
		UnrestrictedCount: unrestrictedCount,
	})
}

// Grant adds a project to a key's project_ids list.
// POST /projects/{id}/access
func (h *ProjectAccessHandler) Grant(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	var req GrantAccessRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	v := validate.New()
	v.Required(req.KeyID, "key_id")
	if err := v.Error(); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	key, err := h.authService.Get(ctx, req.KeyID)
	if err != nil {
		if errors.Is(err, auth.ErrKeyNotFound) {
			api.WriteNotFound(w, r, "key not found")
			return
		}
		api.WriteInternalError(w, r, "failed to get key")
		return
	}

	if !key.IsActive() {
		api.WriteBadRequest(w, r, "key is not active")
		return
	}

	// Unrestricted keys already have access
	if key.ProjectIDs == nil {
		api.WriteBadRequest(w, r, "key already has unrestricted access to all projects")
		return
	}

	// Admin-scoped keys already have full access
	if key.HasScope(domain.ScopeAdmin) {
		api.WriteBadRequest(w, r, "key with admin scope already has full access")
		return
	}

	// Check if already granted
	pid := domain.ProjectID(projectID)
	for _, existing := range key.ProjectIDs {
		if existing == pid {
			api.WriteSuccess(w, r, map[string]string{
				"status":     "already_granted",
				"project_id": projectID,
				"key_id":     req.KeyID,
			})
			return
		}
	}

	// Append and update
	newIDs := append(key.ProjectIDs, pid)
	if err := h.authService.Update(ctx, req.KeyID, port.APIKeyUpdate{ProjectIDs: &newIDs}); err != nil {
		api.WriteInternalError(w, r, "failed to grant access")
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":     "granted",
		"project_id": projectID,
		"key_id":     req.KeyID,
	})
}

// Revoke removes a project from a key's project_ids list.
// DELETE /projects/{id}/access/{keyId}
func (h *ProjectAccessHandler) Revoke(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	keyID := chi.URLParam(r, "keyId")

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	key, err := h.authService.Get(ctx, keyID)
	if err != nil {
		if errors.Is(err, auth.ErrKeyNotFound) {
			api.WriteNotFound(w, r, "key not found")
			return
		}
		api.WriteInternalError(w, r, "failed to get key")
		return
	}

	if key.ProjectIDs == nil {
		api.WriteBadRequest(w, r, "key has unrestricted access; use PATCH /keys/{id} to restrict it first")
		return
	}

	// Filter out the project ID
	pid := domain.ProjectID(projectID)
	newIDs := make([]domain.ProjectID, 0, len(key.ProjectIDs))
	for _, existing := range key.ProjectIDs {
		if existing != pid {
			newIDs = append(newIDs, existing)
		}
	}

	if err := h.authService.Update(ctx, keyID, port.APIKeyUpdate{ProjectIDs: &newIDs}); err != nil {
		api.WriteInternalError(w, r, "failed to revoke access")
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":     "revoked",
		"project_id": projectID,
		"key_id":     keyID,
	})
}
