package admin

import (
	"context"
	"crypto/subtle"
	"errors"
	"net/http"
	"strings"
	"time"

	"bookra/apps/backend/internal/db"
	"bookra/apps/backend/internal/domain"

	"github.com/gin-gonic/gin"
)

var (
	ErrUnauthorized      = errors.New("unauthorized")
	ErrForbidden         = errors.New("forbidden: admin access required")
	ErrInvalidAdminCreds = errors.New("invalid admin credentials")
)

type Service struct {
	repo        db.Repository
	adminEmail  string
	adminKey    string
}

func NewService(repo db.Repository, adminEmail, adminKey string) *Service {
	return &Service{
		repo:       repo,
		adminEmail: adminEmail,
		adminKey:   adminKey,
	}
}

// IsConfigured returns true if admin credentials are set
func (s *Service) IsConfigured() bool {
	return s.adminEmail != "" && s.adminKey != ""
}

// ValidateAdminLogin checks if the provided credentials match the admin credentials
// Uses constant-time comparison to prevent timing attacks
func (s *Service) ValidateAdminLogin(email, key string) bool {
	if !s.IsConfigured() {
		return false
	}

	emailMatch := subtle.ConstantTimeCompare([]byte(email), []byte(s.adminEmail)) == 1
	keyMatch := subtle.ConstantTimeCompare([]byte(key), []byte(s.adminKey)) == 1

	return emailMatch && keyMatch
}

// RequireAdmin is middleware that checks for admin authentication
// It supports two modes:
// 1. Admin credentials via X-Admin-Email and X-Admin-Key headers (for API access)
// 2. Session-based auth where the user has role "admin" or "superadmin"
func RequireAdmin(adminSvc *Service, authSvc interface{ IsAdmin(ctx context.Context, userID string) (bool, error) }) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Check for admin header credentials (direct admin login)
		adminEmail := c.GetHeader("X-Admin-Email")
		adminKey := c.GetHeader("X-Admin-Key")

		if adminEmail != "" && adminKey != "" {
			if adminSvc.ValidateAdminLogin(adminEmail, adminKey) {
				c.Set("isAdmin", true)
				c.Set("adminMode", "credentials")
				c.Next()
				return
			}
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid admin credentials"})
			return
		}

		// Check for Bearer token with admin role
		auth := c.GetHeader("Authorization")
		if auth != "" && strings.HasPrefix(auth, "Bearer ") {
			// The auth middleware should have already validated the token
			// and set the user info in context
			userID, exists := c.Get("userID")
			if !exists {
				c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
				return
			}

			isAdmin, err := authSvc.IsAdmin(c.Request.Context(), userID.(string))
			if err != nil {
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to check admin status"})
				return
			}

			if isAdmin {
				c.Set("isAdmin", true)
				c.Set("adminMode", "session")
				c.Next()
				return
			}
		}

		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "admin access required"})
	}
}

// GetDashboardStats returns platform-wide statistics for admin dashboard
func (s *Service) GetDashboardStats(ctx context.Context) (domain.AdminDashboardStats, error) {
	stats, err := s.repo.GetPlatformStats(ctx)
	if err != nil {
		return domain.AdminDashboardStats{}, err
	}

	return domain.AdminDashboardStats{
		TotalTenants:        stats.TotalTenants,
		TotalUsers:          stats.TotalUsers,
		ActiveSubscriptions: stats.ActiveSubscriptions,
		TrialSubscriptions:  stats.TrialSubscriptions,
		BookingsThisMonth:   stats.BookingsThisMonth,
		RevenueThisMonthCents: stats.RevenueThisMonth,
	}, nil
}

// ListTenants returns paginated list of all tenants
func (s *Service) ListTenants(ctx context.Context, page, pageSize int) (domain.AdminTenantList, error) {
	if page < 1 {
		page = 1
	}
	if pageSize < 1 || pageSize > 100 {
		pageSize = 20
	}

	offset := (page - 1) * pageSize

	tenants, total, err := s.repo.ListAllTenants(ctx, pageSize, offset)
	if err != nil {
		return domain.AdminTenantList{}, err
	}

	result := domain.AdminTenantList{
		Total:    total,
		Page:     page,
		PageSize: pageSize,
		Tenants:  make([]domain.AdminTenant, len(tenants)),
	}

	for i, t := range tenants {
		result.Tenants[i] = domain.AdminTenant{
			ID:                t.ID,
			Slug:              t.Slug,
			Name:              t.Name,
			PlanCode:          t.PlanCode,
			SubscriptionStatus: t.SubscriptionStatus,
			BillingProvider:   t.BillingProvider,
		}
	}

	return result, nil
}

// ListUsers returns paginated list of all users
func (s *Service) ListUsers(ctx context.Context, page, pageSize int) (domain.AdminUserList, error) {
	if page < 1 {
		page = 1
	}
	if pageSize < 1 || pageSize > 100 {
		pageSize = 20
	}

	offset := (page - 1) * pageSize

	users, total, err := s.repo.ListAllUsers(ctx, pageSize, offset)
	if err != nil {
		return domain.AdminUserList{}, err
	}

	result := domain.AdminUserList{
		Total:    total,
		Page:     page,
		PageSize: pageSize,
		Users:    make([]domain.AdminUser, len(users)),
	}

	for i, u := range users {
		result.Users[i] = domain.AdminUser{
			ID:            u.ID.String(),
			Email:         u.Email,
			Name:          stringPtrToStr(u.Name),
			EmailVerified: u.EmailVerified,
			Provider:      u.Provider,
			Role:          u.Role,
			CreatedAt:     u.CreatedAt,
		}
	}

	return result, nil
}

// UpdateUserRole changes a user's role
func (s *Service) UpdateUserRole(ctx context.Context, adminUserID, targetUserID, newRole string, ip, userAgent string) error {
	// Validate role
	validRoles := map[string]bool{
		"user":      true,
		"admin":     true,
		"superadmin": true,
	}
	if !validRoles[newRole] {
		return errors.New("invalid role")
	}

	if err := s.repo.UpdateUserRole(ctx, targetUserID, newRole); err != nil {
		return err
	}

	// Log the action
	return s.repo.CreateAdminAuditLog(ctx, db.AdminAuditLogParams{
		AdminUserID:  adminUserID,
		Action:       "update_user_role",
		ResourceType: "user",
		ResourceID:   targetUserID,
		Details: map[string]any{
			"newRole": newRole,
		},
		IPAddress: ip,
		UserAgent: userAgent,
	})
}

// SyncTenantSubscription manually syncs a tenant's subscription from Stripe
func (s *Service) SyncTenantSubscription(ctx context.Context, tenantID string) error {
	// This will be called from the billing service
	return nil
}

func stringPtrToStr(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

func init() {
	// Ensure time package is imported
	_ = time.Now()
}
