package handler

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/pagination"
	"errors"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

type ApiKeyHandler struct {
	DB *gorm.DB
}

func NewApiKeyHandler(db *gorm.DB) *ApiKeyHandler {
	return &ApiKeyHandler{DB: db}
}

type apiKeySummary struct {
	ID         string    `json:"id"`
	Name       string    `json:"name"`
	ClientID   string    `json:"client_id"`
	Scopes     []string  `json:"scopes"`
	Status     string    `json:"status"`
	LastUsedAt *string   `json:"lastUsedAt"`
	CreatedAt  time.Time `json:"createdAt"`
}

type apiKeyListResponse struct {
	Items      []apiKeySummary `json:"items"`
	Total      int64           `json:"total"`
	Limit      int             `json:"limit"`
	Offset     int             `json:"offset"`
	Cursor     string          `json:"cursor,omitempty"`
	NextCursor string          `json:"nextCursor,omitempty"`
}

func apiKeyToSummary(k domain.ApiKey) apiKeySummary {
	lastUsed := ""
	if k.LastUsedAt != nil {
		lastUsed = k.LastUsedAt.Format(time.RFC3339)
	}
	return apiKeySummary{
		ID:         k.ID,
		Name:       k.Name,
		ClientID:   k.ClientID,
		Scopes:     strings.Fields(strings.ReplaceAll(k.Scopes, ",", " ")),
		Status:     k.Status,
		LastUsedAt: &lastUsed,
		CreatedAt:  k.CreatedAt,
	}
}

func apiKeyWithUpdatedScopes(k domain.ApiKey, scopes []string) domain.ApiKey {
	k.Scopes = strings.Join(normalizeApiKeyScopes(scopes), " ")
	return k
}

func apiKeyWithRotatedSecretHash(k domain.ApiKey, hashedSecret string) domain.ApiKey {
	k.ClientSecretHash = hashedSecret
	return k
}

func normalizeApiKeyScopes(scopes []string) []string {
	seen := make(map[string]struct{}, len(scopes))
	normalized := make([]string, 0, len(scopes))
	for _, scope := range scopes {
		scope = strings.TrimSpace(scope)
		if scope == "" {
			continue
		}
		if _, exists := seen[scope]; exists {
			continue
		}
		seen[scope] = struct{}{}
		normalized = append(normalized, scope)
	}
	return normalized
}

func (h *ApiKeyHandler) ListApiKeys(c *fiber.Ctx) error {
	if h.DB == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "database not available")
	}

	limit := c.QueryInt("limit", 50)
	offset := c.QueryInt("offset", 0)
	cursorRaw := strings.TrimSpace(c.Query("cursor"))
	if limit <= 0 {
		limit = 50
	}
	if offset < 0 {
		offset = 0
	}

	var total int64
	if err := h.DB.Model(&domain.ApiKey{}).Count(&total).Error; err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	var keys []domain.ApiKey
	query := h.DB.Order("created_at desc, id desc").Limit(limit + 1)
	if cursorRaw != "" {
		cursor, err := pagination.Decode(cursorRaw)
		if err != nil {
			return errorJSON(c, fiber.StatusBadRequest, "invalid cursor")
		}
		query = pagination.ApplyCreatedAtIDCursor(query, cursor, "created_at", "id")
		offset = 0
	} else {
		query = query.Offset(offset)
	}

	if err := query.Find(&keys).Error; err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	nextCursor := ""
	hasMore := len(keys) > limit
	if len(keys) > limit {
		keys = keys[:limit]
	}
	if cursorRaw == "" && total > int64(offset+len(keys)) {
		hasMore = true
	}
	if hasMore && len(keys) > 0 {
		last := keys[len(keys)-1]
		nextCursor = pagination.Encode(last.CreatedAt, last.ID)
	}

	items := make([]apiKeySummary, 0, len(keys))
	for _, k := range keys {
		items = append(items, apiKeyToSummary(k))
	}

	return c.JSON(apiKeyListResponse{
		Items:      items,
		Total:      total,
		Limit:      limit,
		Offset:     offset,
		Cursor:     cursorRaw,
		NextCursor: nextCursor,
	})
}

func (h *ApiKeyHandler) CreateApiKey(c *fiber.Ctx) error {
	if h.DB == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "database not available")
	}

	var req struct {
		Name   string   `json:"name"`
		Scopes []string `json:"scopes"`
	}
	if err := c.BodyParser(&req); err != nil {
		return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
	}

	if strings.TrimSpace(req.Name) == "" {
		return errorJSON(c, fiber.StatusBadRequest, "name is required")
	}
	req.Scopes = normalizeApiKeyScopes(req.Scopes)
	if len(req.Scopes) == 0 {
		return errorJSON(c, fiber.StatusBadRequest, "at least one scope is required")
	}

	// Generate Client ID (16 chars hex)
	clientID := GenerateSecureToken(8)

	// Generate plain secret (16 chars hex)
	plainSecret := GenerateSecureToken(8)

	hashedSecret, err := bcrypt.GenerateFromPassword([]byte(plainSecret), bcrypt.DefaultCost)
	if err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, "failed to hash secret")
	}

	apiKey := domain.ApiKey{
		Name:             req.Name,
		ClientID:         clientID,
		ClientSecretHash: string(hashedSecret),
		Scopes:           strings.Join(req.Scopes, " "),
		Status:           "active",
	}

	if err := h.DB.Create(&apiKey).Error; err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	// Return summary + PLAIN SECRET (only this time)
	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"apiKey":       apiKeyToSummary(apiKey),
		"clientSecret": plainSecret, // VERY IMPORTANT: user must save this now
	})
}

func (h *ApiKeyHandler) UpdateApiKey(c *fiber.Ctx) error {
	if h.DB == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "database not available")
	}

	id := c.Params("id")
	if id == "" {
		return errorJSON(c, fiber.StatusBadRequest, "id is required")
	}

	var req struct {
		Scopes []string `json:"scopes"`
	}
	if err := c.BodyParser(&req); err != nil {
		return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
	}
	req.Scopes = normalizeApiKeyScopes(req.Scopes)
	if len(req.Scopes) == 0 {
		return errorJSON(c, fiber.StatusBadRequest, "at least one scope is required")
	}

	var apiKey domain.ApiKey
	if err := h.DB.First(&apiKey, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorJSON(c, fiber.StatusNotFound, "api key not found")
		}
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	apiKey = apiKeyWithUpdatedScopes(apiKey, req.Scopes)
	if err := h.DB.Model(&domain.ApiKey{}).Where("id = ?", id).Update("scopes", apiKey.Scopes).Error; err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	return c.JSON(apiKeyToSummary(apiKey))
}

func (h *ApiKeyHandler) RotateApiKeySecret(c *fiber.Ctx) error {
	if h.DB == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "database not available")
	}

	id := c.Params("id")
	if id == "" {
		return errorJSON(c, fiber.StatusBadRequest, "id is required")
	}

	var apiKey domain.ApiKey
	if err := h.DB.First(&apiKey, "id = ?", id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorJSON(c, fiber.StatusNotFound, "api key not found")
		}
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	plainSecret := GenerateSecureToken(8)
	hashedSecret, err := bcrypt.GenerateFromPassword([]byte(plainSecret), bcrypt.DefaultCost)
	if err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, "failed to hash secret")
	}

	apiKey = apiKeyWithRotatedSecretHash(apiKey, string(hashedSecret))
	if err := h.DB.Model(&domain.ApiKey{}).Where("id = ?", id).Update("client_secret_hash", apiKey.ClientSecretHash).Error; err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	return c.JSON(fiber.Map{
		"apiKey":       apiKeyToSummary(apiKey),
		"clientSecret": plainSecret,
	})
}

func (h *ApiKeyHandler) DeleteApiKey(c *fiber.Ctx) error {
	if h.DB == nil {
		return errorJSON(c, fiber.StatusServiceUnavailable, "database not available")
	}

	id := c.Params("id")
	if id == "" {
		return errorJSON(c, fiber.StatusBadRequest, "id is required")
	}

	if err := h.DB.Delete(&domain.ApiKey{}, "id = ?", id).Error; err != nil {
		return errorJSON(c, fiber.StatusInternalServerError, err.Error())
	}

	return c.SendStatus(fiber.StatusNoContent)
}
