package handler

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/repository"
	"baron-sso-backend/internal/service"
	"context"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
)

type adminHydraClientLister interface {
	ListClients(ctx context.Context, limit, offset int) ([]domain.HydraClient, error)
}

type AdminHandler struct {
	Keto                 service.KetoService
	KetoOutbox           repository.KetoOutboxRepository
	RPUsageQueries       domain.RPUsageQueryRepository
	TenantRepo           repository.TenantRepository
	Hydra                adminHydraClientLister
	AuditRepo            domain.AuditRepository
	UserProjectionRepo   repository.UserProjectionRepository
	UserProjectionSyncer service.UserProjectionReconciler
	IntegrityChecker     repository.DataIntegrityChecker
}

func NewAdminHandler(keto service.KetoService, ketoOutbox repository.KetoOutboxRepository) *AdminHandler {
	return &AdminHandler{
		Keto:       keto,
		KetoOutbox: ketoOutbox,
	}
}

func (h *AdminHandler) GetRPUsageDaily(c *fiber.Ctx) error {
	if h == nil || h.RPUsageQueries == nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "rp usage query service unavailable",
		})
	}
	days := 14
	if raw := c.Query("days"); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil {
			days = parsed
		}
	}
	period := normalizeRPUsagePeriod(c.Query("period"))
	tenantID, allowed := h.authorizedRPUsageTenantID(c, strings.TrimSpace(c.Query("tenantId")))
	if !allowed {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
			"error": "forbidden: tenant rp usage stats permission denied",
		})
	}
	items, err := h.RPUsageQueries.FindRPUsage(c.Context(), domain.RPUsageQuery{
		Days:     days,
		Period:   period,
		TenantID: tenantID,
	})
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": err.Error(),
		})
	}
	return c.JSON(fiber.Map{
		"items":    items,
		"days":     days,
		"period":   period,
		"tenantId": tenantID,
	})
}

func normalizeRPUsagePeriod(period string) string {
	switch strings.ToLower(strings.TrimSpace(period)) {
	case "week":
		return "week"
	case "month":
		return "month"
	default:
		return "day"
	}
}

func (h *AdminHandler) authorizedRPUsageTenantID(c *fiber.Ctx, requestedTenantID string) (string, bool) {
	profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
	if profile != nil && domain.NormalizeRole(profile.Role) == domain.RoleSuperAdmin {
		return requestedTenantID, true
	}
	tenantID := requestedTenantID
	if tenantID == "" && profile != nil && profile.TenantID != nil {
		tenantID = strings.TrimSpace(*profile.TenantID)
	}
	if tenantID == "" {
		return "", false
	}
	if h == nil || h.Keto == nil || profile == nil || strings.TrimSpace(profile.ID) == "" {
		return "", false
	}
	allowed, err := h.Keto.CheckPermission(c.Context(), "User:"+profile.ID, "Tenant", tenantID, "view_rp_usage_stats")
	if err != nil || !allowed {
		return "", false
	}
	return tenantID, true
}

func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error {
	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
}

func requireSuperAdminProfile(c *fiber.Ctx) bool {
	profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
	if profile == nil || domain.NormalizeRole(profile.Role) != domain.RoleSuperAdmin {
		_ = c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: super_admin required"})
		return false
	}
	return true
}

func (h *AdminHandler) GetUserProjectionStatus(c *fiber.Ctx) error {
	if !requireSuperAdminProfile(c) {
		return nil
	}
	if h == nil || h.UserProjectionRepo == nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "user projection service unavailable"})
	}
	status, err := h.UserProjectionRepo.GetStatus(c.Context())
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(status)
}

func (h *AdminHandler) ReconcileUserProjection(c *fiber.Ctx) error {
	if !requireSuperAdminProfile(c) {
		return nil
	}
	if h == nil || h.UserProjectionSyncer == nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "user projection sync service unavailable"})
	}
	count, err := h.UserProjectionSyncer.Reconcile(c.Context())
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{
		"status":      "success",
		"syncedUsers": count,
		"updatedAt":   time.Now().UTC().Format(time.RFC3339),
	})
}

func (h *AdminHandler) ResetUserProjection(c *fiber.Ctx) error {
	return h.ReconcileUserProjection(c)
}

func (h *AdminHandler) GetDataIntegrity(c *fiber.Ctx) error {
	if !requireSuperAdminProfile(c) {
		return nil
	}
	if h == nil || h.IntegrityChecker == nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "data integrity checker unavailable"})
	}
	report, err := h.IntegrityChecker.CheckDataIntegrity(c.Context())
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(report)
}

func (h *AdminHandler) ListOrphanUserLoginIDs(c *fiber.Ctx) error {
	if !requireSuperAdminProfile(c) {
		return nil
	}
	if h == nil || h.IntegrityChecker == nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "data integrity checker unavailable"})
	}
	items, err := h.IntegrityChecker.ListOrphanUserLoginIDs(c.Context())
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(fiber.Map{
		"items": items,
		"total": len(items),
	})
}

func (h *AdminHandler) DeleteOrphanUserLoginIDs(c *fiber.Ctx) error {
	if !requireSuperAdminProfile(c) {
		return nil
	}
	if h == nil || h.IntegrityChecker == nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "data integrity checker unavailable"})
	}
	var req struct {
		IDs []string `json:"ids"`
	}
	if err := c.BodyParser(&req); err != nil {
		return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
	}
	result, err := h.IntegrityChecker.DeleteOrphanUserLoginIDs(c.Context(), req.IDs)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}
	return c.JSON(result)
}

// GetSystemStats returns runtime statistics for monitoring
func (h *AdminHandler) GetSystemStats(c *fiber.Ctx) error {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	ctx := c.Context()

	stats := fiber.Map{
		"totalTenants":   h.countTenants(ctx),
		"totalUsers":     h.countUsers(ctx),
		"oidcClients":    h.countOIDCClients(ctx),
		"auditEvents24h": h.countAuditEventsSince(ctx, time.Now().UTC().Add(-24*time.Hour)),
		"goroutines":     runtime.NumGoroutine(),
		"cpus":           runtime.NumCPU(),
		"memory": fiber.Map{
			"alloc":      m.Alloc,
			"totalAlign": m.TotalAlloc,
			"sys":        m.Sys,
			"numGC":      m.NumGC,
		},
		"timestamp": time.Now(),
	}

	return c.Status(fiber.StatusOK).JSON(stats)
}

func (h *AdminHandler) countTenants(ctx context.Context) int64 {
	if h == nil || h.TenantRepo == nil {
		return 0
	}
	_, total, err := h.TenantRepo.List(ctx, 1, 0, "")
	if err != nil {
		return 0
	}
	return total
}

func (h *AdminHandler) countUsers(ctx context.Context) int64 {
	if h == nil || h.UserProjectionRepo == nil {
		return 0
	}
	status, err := h.UserProjectionRepo.GetStatus(ctx)
	if err != nil {
		return 0
	}
	return status.ProjectedUsers
}

func (h *AdminHandler) countOIDCClients(ctx context.Context) int64 {
	if h == nil || h.Hydra == nil {
		return 0
	}
	const pageSize = 500
	var total int64
	for offset := 0; ; offset += pageSize {
		clients, err := h.Hydra.ListClients(ctx, pageSize, offset)
		if err != nil {
			return total
		}
		for _, client := range clients {
			if isHiddenSystemClient(client) {
				continue
			}
			total++
		}
		if len(clients) < pageSize {
			break
		}
	}
	return total
}

func (h *AdminHandler) countAuditEventsSince(ctx context.Context, since time.Time) int64 {
	if h == nil || h.AuditRepo == nil {
		return 0
	}
	count, err := h.AuditRepo.CountEventsSince(ctx, since)
	if err == nil && count > 0 {
		return count
	}
	logs, pageErr := h.AuditRepo.FindPage(ctx, 10000, nil, "")
	if pageErr != nil {
		return count
	}
	var fallbackCount int64
	for _, log := range logs {
		if !log.Timestamp.Before(since) {
			fallbackCount++
		}
	}
	return fallbackCount
}
