package handlers

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

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

// KeysHandler handles API key management endpoints.
type KeysHandler struct {
	authService *auth.Service
}

// NewKeysHandler creates a new keys handler.
func NewKeysHandler(authService *auth.Service) *KeysHandler {
	return &KeysHandler{authService: authService}
}

// Mount registers the keys routes.
func (h *KeysHandler) Mount(r api.Router) {
	r.Route("/keys", func(r chi.Router) {
		// All key endpoints require authentication (handled by global middleware)
		r.With(auth.RequireScope(auth.ScopeKeysRead, auth.ScopeAdmin)).Get("/", h.List)
		r.With(auth.RequireScope(auth.ScopeKeysWrite, auth.ScopeAdmin)).Post("/", h.Create)
		r.With(auth.RequireScope(auth.ScopeKeysRead, auth.ScopeAdmin)).Get("/{id}", h.Get)
		r.With(auth.RequireScope(auth.ScopeKeysWrite, auth.ScopeAdmin)).Delete("/{id}", h.Revoke)
	})
}

// CreateKeyRequest is the JSON body for creating a key.
type CreateKeyRequest struct {
	Name       string   `json:"name"`
	Scopes     []string `json:"scopes"`
	ProjectIDs []string `json:"project_ids,omitempty"` // null = all projects
	ExpiresIn  string   `json:"expires_in,omitempty"`  // "30d", "60d", "90d", "1y", "never"
	AllowedIPs []string `json:"allowed_ips,omitempty"` // CIDR notation, e.g., ["192.168.1.0/24"]; null = no restriction
}

// KeyResponse is the JSON response for a key (without secret).
type KeyResponse struct {
	ID         string   `json:"id"`
	Name       string   `json:"name"`
	KeyPrefix  string   `json:"key_prefix"`
	Scopes     []string `json:"scopes"`
	ProjectIDs []string `json:"project_ids,omitempty"`
	AllowedIPs []string `json:"allowed_ips,omitempty"`
	CreatedAt  string   `json:"created_at"`
	ExpiresAt  *string  `json:"expires_at,omitempty"`
	LastUsedAt *string  `json:"last_used_at,omitempty"`
	RevokedAt  *string  `json:"revoked_at,omitempty"`
	CreatedBy  string   `json:"created_by"`
	Active     bool     `json:"active"`
}

// CreateKeyResponse includes the secret (shown only once).
type CreateKeyResponse struct {
	Key    KeyResponse `json:"key"`
	Secret string      `json:"secret"`
}

// apiKeyToResponse converts an APIKey to a JSON response.
func apiKeyToResponse(k *auth.APIKey) KeyResponse {
	resp := KeyResponse{
		ID:        string(k.ID),
		Name:      k.Name,
		KeyPrefix: k.KeyPrefix,
		Scopes:    auth.ScopesToStrings(k.Scopes),
		CreatedAt: k.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
		CreatedBy: k.CreatedBy,
		Active:    k.IsActive(),
	}

	if k.ProjectIDs != nil {
		resp.ProjectIDs = make([]string, len(k.ProjectIDs))
		for i, pid := range k.ProjectIDs {
			resp.ProjectIDs[i] = string(pid)
		}
	}

	if k.AllowedIPs != nil {
		resp.AllowedIPs = k.AllowedIPs
	}

	if k.ExpiresAt != nil {
		s := k.ExpiresAt.Format("2006-01-02T15:04:05Z07:00")
		resp.ExpiresAt = &s
	}

	if k.LastUsedAt != nil {
		s := k.LastUsedAt.Format("2006-01-02T15:04:05Z07:00")
		resp.LastUsedAt = &s
	}

	if k.RevokedAt != nil {
		s := k.RevokedAt.Format("2006-01-02T15:04:05Z07:00")
		resp.RevokedAt = &s
	}

	return resp
}

// List returns all API keys.
// GET /keys
func (h *KeysHandler) List(w http.ResponseWriter, r *http.Request) {
	keys, err := h.authService.List(r.Context())
	if err != nil {
		api.WriteInternalError(w, r, "Failed to list keys")
		return
	}

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

	api.WriteSuccess(w, r, resp)
}

// Create generates a new API key.
// POST /keys
func (h *KeysHandler) Create(w http.ResponseWriter, r *http.Request) {
	var req CreateKeyRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	v := validate.New()
	v.Required(req.Name, "name")
	v.RequiredSlice(req.Scopes, "scopes")
	if err := v.Error(); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	// Validate scopes
	scopes := auth.ScopesFromStrings(req.Scopes)
	if !auth.ValidateScopes(scopes) {
		api.WriteBadRequest(w, r, "invalid scope(s)")
		return
	}

	// Parse expiration
	expiresIn, err := auth.ParseExpiration(req.ExpiresIn)
	if err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	// Validate allowed_ips CIDR format
	for _, cidr := range req.AllowedIPs {
		if err := validateCIDROrIP(cidr); err != nil {
			api.WriteBadRequest(w, r, "invalid allowed_ips: "+cidr+" is not a valid CIDR or IP address")
			return
		}
	}

	// Get creator from authenticated key
	creator := "admin"
	if apiKey := auth.GetAPIKey(r.Context()); apiKey != nil && string(apiKey.ID) != "admin" {
		creator = string(apiKey.ID)
	}

	result, err := h.authService.Create(r.Context(), auth.CreateKeyRequest{
		Name:       req.Name,
		Scopes:     scopes,
		ProjectIDs: req.ProjectIDs,
		AllowedIPs: req.AllowedIPs,
		ExpiresIn:  expiresIn,
		CreatedBy:  creator,
	})
	if err != nil {
		api.WriteInternalError(w, r, "Failed to create key")
		return
	}

	api.WriteCreated(w, r, CreateKeyResponse{
		Key:    apiKeyToResponse(result.Key),
		Secret: result.Secret,
	})
}

// validateCIDROrIP validates that a string is either a valid CIDR notation or a valid IP address.
func validateCIDROrIP(cidr string) error {
	// Try parsing as CIDR first
	_, _, err := net.ParseCIDR(cidr)
	if err == nil {
		return nil
	}

	// Try parsing as a single IP address
	ip := net.ParseIP(cidr)
	if ip != nil {
		return nil
	}

	return err
}

// Get returns a single API key.
// GET /keys/{id}
func (h *KeysHandler) Get(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	key, err := h.authService.Get(r.Context(), id)
	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
	}

	api.WriteSuccess(w, r, apiKeyToResponse(key))
}

// Revoke marks an API key as revoked.
// DELETE /keys/{id}
func (h *KeysHandler) Revoke(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	if err := h.authService.Revoke(r.Context(), id); err != nil {
		if errors.Is(err, auth.ErrKeyNotFound) {
			api.WriteNotFound(w, r, "Key not found")
			return
		}
		api.WriteInternalError(w, r, "Failed to revoke key")
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status": "revoked",
		"id":     id,
	})
}
