package admin

import (
	"context"
	"time"

	"github.com/getsentry/sentry-go"
	"github.com/google/uuid"
	"github.com/warmbly/warmbly/internal/errx"
	"github.com/warmbly/warmbly/internal/models"
	"github.com/warmbly/warmbly/internal/repository"
)

// AdminService defines the interface for admin operations
type AdminService interface {
	// Mailbox admin
	SearchMailboxes(ctx context.Context, search *models.AdminMailboxSearch) (*models.AdminMailboxesResult, *errx.Error)

	// User Management
	SearchUsers(ctx context.Context, search *models.AdminUserSearch) (*models.AdminUsersResult, *errx.Error)
	GetUserDetail(ctx context.Context, userID uuid.UUID) (*models.AdminUserDetail, *errx.Error)
	GetUserPreview(ctx context.Context, userID uuid.UUID) (*models.AdminUserPreview, *errx.Error)
	BanUser(ctx context.Context, adminID, userID uuid.UUID, reason string, scope models.BanScope, ipAddress, userAgent string) *errx.Error
	UnbanUser(ctx context.Context, adminID, userID uuid.UUID, reason string, ipAddress, userAgent string) *errx.Error
	GetUserBans(ctx context.Context, userID uuid.UUID) ([]models.UserBan, *errx.Error)
	GetUserCampaigns(ctx context.Context, userID uuid.UUID, cursor *uuid.UUID, limit int) (*models.AdminCampaignsResult, *errx.Error)
	GetUserEmails(ctx context.Context, userID uuid.UUID, cursor *uuid.UUID, limit int) ([]models.AdminWorkerEmail, *models.Pagination, *errx.Error)
	GetUserRateLimits(ctx context.Context, userID uuid.UUID) (*models.AdminUserRateLimits, *errx.Error)
	UpdateUserRateLimits(ctx context.Context, adminID, userID uuid.UUID, update *models.UpdateUserRateLimitsRequest, ipAddress, userAgent string) (*models.AdminUserRateLimits, *errx.Error)

	// Worker Management
	ListWorkers(ctx context.Context, cursor *uuid.UUID, limit int) (*models.AdminWorkersResult, *errx.Error)
	GetWorkerDetail(ctx context.Context, workerID uuid.UUID) (*models.AdminWorkerDetail, *errx.Error)
	UpdateWorker(ctx context.Context, adminID, workerID uuid.UUID, update *models.AdminUpdateWorker, ipAddress, userAgent string) *errx.Error
	GetWorkerEmails(ctx context.Context, workerID uuid.UUID, cursor *uuid.UUID, limit int) ([]models.AdminWorkerEmail, *models.Pagination, *errx.Error)
	GetWorkerStats(ctx context.Context, workerID uuid.UUID) (*models.WorkerStats, *errx.Error)
	ReassignEmails(ctx context.Context, adminID uuid.UUID, emailIDs []uuid.UUID, newWorkerID uuid.UUID, ipAddress, userAgent string) *errx.Error

	// Warmup Management
	ListWarmupPools(ctx context.Context) ([]models.WarmupPoolInfo, *errx.Error)
	GetPoolParticipants(ctx context.Context, poolType string, cursor *uuid.UUID, limit int) (*models.WarmupPoolParticipantsResult, *errx.Error)
	ListBlockedAccounts(ctx context.Context, cursor *uuid.UUID, limit int) (*models.AdminBlockedAccountsResult, *errx.Error)
	BlockAccount(ctx context.Context, adminID, accountID uuid.UUID, reason string, ipAddress, userAgent string) *errx.Error
	UnblockAccount(ctx context.Context, adminID, accountID uuid.UUID, ipAddress, userAgent string) *errx.Error

	// Appeals
	ListAppeals(ctx context.Context, status string, cursor *uuid.UUID, limit int) (*models.WarmupAppealsResult, *errx.Error)
	GetAppeal(ctx context.Context, appealID uuid.UUID) (*models.WarmupAppeal, *errx.Error)
	ReviewAppeal(ctx context.Context, adminID, appealID uuid.UUID, approved bool, notes string, ipAddress, userAgent string) *errx.Error

	// Campaign Management
	SearchCampaigns(ctx context.Context, search *models.AdminCampaignSearch) (*models.AdminCampaignsResult, *errx.Error)
	GetCampaignDetail(ctx context.Context, campaignID uuid.UUID) (*models.AdminCampaignDetail, *errx.Error)
	StopCampaign(ctx context.Context, adminID, campaignID uuid.UUID, reason, ipAddress, userAgent string) *errx.Error

	// Analytics
	GetPlatformOverview(ctx context.Context) (*models.PlatformOverview, *errx.Error)
	GetAnalyticsTrends(ctx context.Context) (*models.AnalyticsTrends, *errx.Error)
	GetDailyEmailStats(ctx context.Context, startDate, endDate time.Time) ([]models.DailyEmailStats, *errx.Error)
	GetHourlyEmailStats(ctx context.Context, date time.Time) ([]models.HourlyEmailStats, *errx.Error)
	GetWorkerLoadStats(ctx context.Context) ([]models.WorkerLoadStats, *errx.Error)
	GetEmailDistribution(ctx context.Context) ([]models.EmailDistribution, *errx.Error)
	GetUserGrowthStats(ctx context.Context, startDate, endDate time.Time) ([]models.UserGrowthStats, *errx.Error)

	// Plans
	ListPlans(ctx context.Context, includePrivate bool) ([]models.Plan, *errx.Error)
	SearchPlansForAdmin(ctx context.Context, search *models.AdminPlanSearch) (*models.AdminPlansResult, *errx.Error)
	CreatePlan(ctx context.Context, adminID uuid.UUID, req *models.CreatePlanRequest, ipAddress, userAgent string) (*models.Plan, *errx.Error)
	GetPlan(ctx context.Context, planID uuid.UUID) (*models.Plan, *errx.Error)
	UpdatePlan(ctx context.Context, adminID, planID uuid.UUID, req *models.UpdatePlanRequest, ipAddress, userAgent string) (*models.Plan, *errx.Error)
	DeletePlan(ctx context.Context, adminID, planID uuid.UUID, ipAddress, userAgent string) *errx.Error

	// Enterprise Inquiries
	ListEnterpriseInquiries(ctx context.Context, search *models.AdminEnterpriseInquirySearch) (*models.AdminEnterpriseInquiriesResult, *errx.Error)
	GetEnterpriseInquiry(ctx context.Context, id uuid.UUID) (*models.AdminEnterpriseInquiry, *errx.Error)
	UpdateEnterpriseInquiry(ctx context.Context, adminID, inquiryID uuid.UUID, update *models.UpdateEnterpriseInquiryRequest, ipAddress, userAgent string) *errx.Error

	// Admin Management
	ListAdmins(ctx context.Context, cursor *uuid.UUID, limit int) (*models.AdminsResult, *errx.Error)
	GrantAdminPermissions(ctx context.Context, adminID, targetUserID uuid.UUID, permissions models.AdminPermission, ipAddress, userAgent string) *errx.Error
	RevokeAdminPermissions(ctx context.Context, adminID, targetUserID uuid.UUID, ipAddress, userAgent string) *errx.Error

	// Audit Logs
	SearchAuditLogs(ctx context.Context, search *models.AdminAuditLogSearch) (*models.AdminAuditLogsResult, *errx.Error)

	// LogAdminAction writes a row to admin_audit_log. Fire-and-forget; safe
	// to call from any handler that has the admin's identity and a target.
	// Used by the new SSH worker / credentials / release / system handlers.
	LogAdminAction(ctx context.Context, adminID uuid.UUID, action, targetType string, targetID *uuid.UUID, details map[string]any, ipAddress, userAgent string)
}

type adminService struct {
	repo repository.AdminRepository
	// The owner-visible campaign activity feed.
	campaignLogRepo repository.CampaignLogRepository
}

// NewService creates a new admin service
func NewService(repo repository.AdminRepository, campaignLogRepo repository.CampaignLogRepository) AdminService {
	return &adminService{repo: repo, campaignLogRepo: campaignLogRepo}
}

// logAction logs an admin action
func (s *adminService) logAction(ctx context.Context, adminID uuid.UUID, action, targetType string, targetID uuid.UUID, details map[string]any, ipAddress, userAgent string) {
	log := &models.AdminAuditLog{
		ID:          uuid.New(),
		AdminUserID: adminID,
		Action:      action,
		TargetType:  targetType,
		TargetID:    targetID,
		Details:     details,
		IPAddress:   ipAddress,
		UserAgent:   userAgent,
		CreatedAt:   time.Now(),
	}
	if err := s.repo.CreateAuditLog(ctx, log); err != nil {
		sentry.CaptureException(err)
	}
}

// LogAdminAction is the public-facing version of logAction. Goroutine-detached
// so handlers can call it without taking the request-cycle hit on the audit
// write. Accepts an optional targetID (uuid.Nil if the action isn't tied to
// a specific entity, e.g. "check releases").
func (s *adminService) LogAdminAction(ctx context.Context, adminID uuid.UUID, action, targetType string, targetID *uuid.UUID, details map[string]any, ipAddress, userAgent string) {
	var tid uuid.UUID
	if targetID != nil {
		tid = *targetID
	}
	// Capture values into the goroutine; ctx is replaced with Background so
	// the audit row still lands even if the request context cancels first.
	go func() {
		s.logAction(context.Background(), adminID, action, targetType, tid, details, ipAddress, userAgent)
	}()
}

// User Management

func (s *adminService) SearchMailboxes(ctx context.Context, search *models.AdminMailboxSearch) (*models.AdminMailboxesResult, *errx.Error) {
	result, err := s.repo.SearchMailboxesForAdmin(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to search mailboxes")
	}
	return result, nil
}

func (s *adminService) SearchUsers(ctx context.Context, search *models.AdminUserSearch) (*models.AdminUsersResult, *errx.Error) {
	result, err := s.repo.SearchUsers(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to search users")
	}
	return result, nil
}

func (s *adminService) GetUserDetail(ctx context.Context, userID uuid.UUID) (*models.AdminUserDetail, *errx.Error) {
	user, err := s.repo.GetUserDetail(ctx, userID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get user detail")
	}
	if user == nil {
		return nil, errx.ErrNotFound
	}
	return user, nil
}

func (s *adminService) GetUserPreview(ctx context.Context, userID uuid.UUID) (*models.AdminUserPreview, *errx.Error) {
	preview, err := s.repo.GetUserPreview(ctx, userID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get user preview")
	}
	if preview == nil {
		return nil, errx.ErrNotFound
	}
	// Same answer as GET /users/:id/rate-limits: no row means the defaults.
	if preview.RateLimits == nil {
		preview.RateLimits = models.DefaultAdminUserRateLimits(userID)
	}
	return preview, nil
}

func (s *adminService) BanUser(ctx context.Context, adminID, userID uuid.UUID, reason string, scope models.BanScope, ipAddress, userAgent string) *errx.Error {
	// Check if user exists
	user, err := s.repo.GetUserDetail(ctx, userID)
	if err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to get user")
	}
	if user == nil {
		return errx.ErrNotFound
	}

	// Check if already banned
	if user.BannedAt != nil {
		return errx.New(errx.BadRequest, "user is already banned")
	}

	// Cannot ban other admins
	if user.AdminPermissions > 0 {
		return errx.New(errx.Forbidden, "cannot ban admin users")
	}

	// Default to login-only ban when the caller leaves scope empty so
	// the legacy "you can't log in" behaviour is preserved.
	if scope == 0 {
		scope = models.BanScopeLogin
	}

	if err := s.repo.BanUser(ctx, userID, adminID, reason, uint32(scope)); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to ban user")
	}

	s.logAction(ctx, adminID, "ban_user", "user", userID, map[string]any{"reason": reason, "scope": uint32(scope)}, ipAddress, userAgent)
	return nil
}

func (s *adminService) UnbanUser(ctx context.Context, adminID, userID uuid.UUID, reason string, ipAddress, userAgent string) *errx.Error {
	// Check if user exists
	user, err := s.repo.GetUserDetail(ctx, userID)
	if err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to get user")
	}
	if user == nil {
		return errx.ErrNotFound
	}

	// Check if not banned
	if user.BannedAt == nil {
		return errx.New(errx.BadRequest, "user is not banned")
	}

	if err := s.repo.UnbanUser(ctx, userID, adminID, reason); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to unban user")
	}

	s.logAction(ctx, adminID, "unban_user", "user", userID, map[string]any{"reason": reason}, ipAddress, userAgent)
	return nil
}

func (s *adminService) GetUserBans(ctx context.Context, userID uuid.UUID) ([]models.UserBan, *errx.Error) {
	bans, err := s.repo.GetUserBans(ctx, userID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get user bans")
	}
	return bans, nil
}

func (s *adminService) GetUserCampaigns(ctx context.Context, userID uuid.UUID, cursor *uuid.UUID, limit int) (*models.AdminCampaignsResult, *errx.Error) {
	search := &models.AdminCampaignSearch{
		UserID: &userID,
		Cursor: cursor,
		Limit:  limit,
	}
	result, err := s.repo.SearchCampaigns(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get user campaigns")
	}
	return result, nil
}

func (s *adminService) GetUserEmails(ctx context.Context, userID uuid.UUID, cursor *uuid.UUID, limit int) ([]models.AdminWorkerEmail, *models.Pagination, *errx.Error) {
	emails, pagination, err := s.repo.GetUserEmails(ctx, userID, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, nil, errx.New(errx.Internal, "failed to get user emails")
	}
	return emails, pagination, nil
}

func (s *adminService) GetUserRateLimits(ctx context.Context, userID uuid.UUID) (*models.AdminUserRateLimits, *errx.Error) {
	limits, err := s.repo.GetUserRateLimits(ctx, userID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get rate limits")
	}
	// No row means the enforcement path falls back to the product defaults.
	if limits == nil {
		return models.DefaultAdminUserRateLimits(userID), nil
	}
	return limits, nil
}

func (s *adminService) UpdateUserRateLimits(ctx context.Context, adminID, userID uuid.UUID, update *models.UpdateUserRateLimitsRequest, ipAddress, userAgent string) (*models.AdminUserRateLimits, *errx.Error) {
	if err := s.repo.UpdateUserRateLimits(ctx, userID, adminID, update); err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to update rate limits")
	}

	s.logAction(ctx, adminID, "update_rate_limits", "user", userID, map[string]any{"limits": update}, ipAddress, userAgent)
	return s.GetUserRateLimits(ctx, userID)
}

// Worker Management

func (s *adminService) ListWorkers(ctx context.Context, cursor *uuid.UUID, limit int) (*models.AdminWorkersResult, *errx.Error) {
	result, err := s.repo.ListWorkers(ctx, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list workers")
	}
	return result, nil
}

func (s *adminService) GetWorkerDetail(ctx context.Context, workerID uuid.UUID) (*models.AdminWorkerDetail, *errx.Error) {
	worker, err := s.repo.GetWorkerDetail(ctx, workerID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get worker detail")
	}
	if worker == nil {
		return nil, errx.ErrNotFound
	}
	return worker, nil
}

func (s *adminService) UpdateWorker(ctx context.Context, adminID, workerID uuid.UUID, update *models.AdminUpdateWorker, ipAddress, userAgent string) *errx.Error {
	if err := s.repo.UpdateWorker(ctx, workerID, update); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to update worker")
	}

	s.logAction(ctx, adminID, "update_worker", "worker", workerID, map[string]any{"update": update}, ipAddress, userAgent)
	return nil
}

func (s *adminService) GetWorkerEmails(ctx context.Context, workerID uuid.UUID, cursor *uuid.UUID, limit int) ([]models.AdminWorkerEmail, *models.Pagination, *errx.Error) {
	emails, pagination, err := s.repo.GetWorkerEmails(ctx, workerID, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, nil, errx.New(errx.Internal, "failed to get worker emails")
	}
	return emails, pagination, nil
}

func (s *adminService) GetWorkerStats(ctx context.Context, workerID uuid.UUID) (*models.WorkerStats, *errx.Error) {
	stats, err := s.repo.GetWorkerStats(ctx, workerID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get worker stats")
	}
	return stats, nil
}

func (s *adminService) ReassignEmails(ctx context.Context, adminID uuid.UUID, emailIDs []uuid.UUID, newWorkerID uuid.UUID, ipAddress, userAgent string) *errx.Error {
	if err := s.repo.ReassignEmails(ctx, emailIDs, newWorkerID); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to reassign emails")
	}

	s.logAction(ctx, adminID, "reassign_emails", "worker", newWorkerID, map[string]any{"email_count": len(emailIDs)}, ipAddress, userAgent)
	return nil
}

// Warmup Management

func (s *adminService) ListWarmupPools(ctx context.Context) ([]models.WarmupPoolInfo, *errx.Error) {
	pools, err := s.repo.ListWarmupPools(ctx)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list warmup pools")
	}
	return pools, nil
}

func (s *adminService) GetPoolParticipants(ctx context.Context, poolType string, cursor *uuid.UUID, limit int) (*models.WarmupPoolParticipantsResult, *errx.Error) {
	result, err := s.repo.GetPoolParticipants(ctx, poolType, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get pool participants")
	}
	return result, nil
}

func (s *adminService) ListBlockedAccounts(ctx context.Context, cursor *uuid.UUID, limit int) (*models.AdminBlockedAccountsResult, *errx.Error) {
	result, err := s.repo.ListBlockedAccounts(ctx, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list blocked accounts")
	}
	return result, nil
}

func (s *adminService) BlockAccount(ctx context.Context, adminID, accountID uuid.UUID, reason string, ipAddress, userAgent string) *errx.Error {
	if err := s.repo.BlockAccount(ctx, accountID, adminID, reason); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to block account")
	}

	s.logAction(ctx, adminID, "block_warmup_account", "email_account", accountID, map[string]any{"reason": reason}, ipAddress, userAgent)
	return nil
}

func (s *adminService) UnblockAccount(ctx context.Context, adminID, accountID uuid.UUID, ipAddress, userAgent string) *errx.Error {
	if err := s.repo.UnblockAccount(ctx, accountID); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to unblock account")
	}

	s.logAction(ctx, adminID, "unblock_warmup_account", "email_account", accountID, nil, ipAddress, userAgent)
	return nil
}

// Appeals

func (s *adminService) ListAppeals(ctx context.Context, status string, cursor *uuid.UUID, limit int) (*models.WarmupAppealsResult, *errx.Error) {
	result, err := s.repo.ListAppeals(ctx, status, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list appeals")
	}
	return result, nil
}

func (s *adminService) GetAppeal(ctx context.Context, appealID uuid.UUID) (*models.WarmupAppeal, *errx.Error) {
	appeal, err := s.repo.GetAppeal(ctx, appealID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get appeal")
	}
	if appeal == nil {
		return nil, errx.ErrNotFound
	}
	return appeal, nil
}

func (s *adminService) ReviewAppeal(ctx context.Context, adminID, appealID uuid.UUID, approved bool, notes string, ipAddress, userAgent string) *errx.Error {
	appeal, err := s.repo.GetAppeal(ctx, appealID)
	if err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to get appeal")
	}
	if appeal == nil {
		return errx.ErrNotFound
	}

	if appeal.Status != models.WarmupAppealStatusPending {
		return errx.New(errx.BadRequest, "appeal has already been reviewed")
	}

	if err := s.repo.ReviewAppeal(ctx, appealID, adminID, approved, notes); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to review appeal")
	}

	action := "reject_appeal"
	if approved {
		action = "approve_appeal"
	}
	s.logAction(ctx, adminID, action, "warmup_appeal", appealID, map[string]any{"notes": notes}, ipAddress, userAgent)
	return nil
}

// Campaign Management

func (s *adminService) SearchCampaigns(ctx context.Context, search *models.AdminCampaignSearch) (*models.AdminCampaignsResult, *errx.Error) {
	result, err := s.repo.SearchCampaigns(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to search campaigns")
	}
	return result, nil
}

func (s *adminService) GetCampaignDetail(ctx context.Context, campaignID uuid.UUID) (*models.AdminCampaignDetail, *errx.Error) {
	campaign, err := s.repo.GetCampaignDetail(ctx, campaignID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get campaign detail")
	}
	if campaign == nil {
		return nil, errx.ErrNotFound
	}
	return campaign, nil
}

func (s *adminService) StopCampaign(ctx context.Context, adminID, campaignID uuid.UUID, reason, ipAddress, userAgent string) *errx.Error {
	campaign, err := s.repo.GetCampaignDetail(ctx, campaignID)
	if err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to get campaign")
	}
	if campaign == nil {
		return errx.ErrNotFound
	}

	// The repository decides: a draft has never sent and a completed campaign
	// never will, and either can become true between here and the UPDATE.
	stopped, err := s.repo.StopCampaign(ctx, campaignID)
	if err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to stop campaign")
	}
	if !stopped {
		return errx.New(errx.BadRequest, "campaign is not running")
	}

	// Without the reason the owner just sees a campaign that went quiet.
	if s.campaignLogRepo != nil {
		if err := s.campaignLogRepo.CreateLog(ctx, &repository.CampaignLogEntry{
			CampaignID: campaignID,
			EventType:  "stopped",
			Message:    "Campaign force-stopped by platform staff: " + reason,
			Metadata:   map[string]interface{}{"reason": reason},
		}); err != nil {
			sentry.CaptureException(err)
		}
	}

	s.logAction(ctx, adminID, "force_stop_campaign", "campaign", campaignID, map[string]any{
		"user_id": campaign.UserID,
		"reason":  reason,
	}, ipAddress, userAgent)
	return nil
}

// Analytics

func (s *adminService) GetPlatformOverview(ctx context.Context) (*models.PlatformOverview, *errx.Error) {
	overview, err := s.repo.GetPlatformOverview(ctx)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get platform overview")
	}
	return overview, nil
}

func (s *adminService) GetAnalyticsTrends(ctx context.Context) (*models.AnalyticsTrends, *errx.Error) {
	trends, err := s.repo.GetAnalyticsTrends(ctx)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get analytics trends")
	}
	return trends, nil
}

func (s *adminService) GetDailyEmailStats(ctx context.Context, startDate, endDate time.Time) ([]models.DailyEmailStats, *errx.Error) {
	stats, err := s.repo.GetDailyEmailStats(ctx, startDate, endDate)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get daily email stats")
	}
	return stats, nil
}

func (s *adminService) GetHourlyEmailStats(ctx context.Context, date time.Time) ([]models.HourlyEmailStats, *errx.Error) {
	stats, err := s.repo.GetHourlyEmailStats(ctx, date)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get hourly email stats")
	}
	return stats, nil
}

func (s *adminService) GetWorkerLoadStats(ctx context.Context) ([]models.WorkerLoadStats, *errx.Error) {
	stats, err := s.repo.GetWorkerLoadStats(ctx)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get worker load stats")
	}
	return stats, nil
}

func (s *adminService) GetEmailDistribution(ctx context.Context) ([]models.EmailDistribution, *errx.Error) {
	dist, err := s.repo.GetEmailDistribution(ctx)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get email distribution")
	}
	return dist, nil
}

func (s *adminService) GetUserGrowthStats(ctx context.Context, startDate, endDate time.Time) ([]models.UserGrowthStats, *errx.Error) {
	stats, err := s.repo.GetUserGrowthStats(ctx, startDate, endDate)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get user growth stats")
	}
	return stats, nil
}

// Plans

func (s *adminService) ListPlans(ctx context.Context, includePrivate bool) ([]models.Plan, *errx.Error) {
	plans, err := s.repo.ListPlans(ctx, includePrivate)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list plans")
	}
	return plans, nil
}

func (s *adminService) SearchPlansForAdmin(ctx context.Context, search *models.AdminPlanSearch) (*models.AdminPlansResult, *errx.Error) {
	result, err := s.repo.SearchPlansForAdmin(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to search plans")
	}
	return result, nil
}

// resolveDuration maps the API's billing period onto a durations row. An
// unknown period is the caller's mistake, not an internal error.
func (s *adminService) resolveDuration(ctx context.Context, d models.Duration) (*uuid.UUID, *errx.Error) {
	if d == "" {
		return nil, errx.New(errx.BadRequest, "duration is required")
	}
	id, err := s.repo.DurationIDByTitle(ctx, string(d))
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to resolve plan duration")
	}
	if id == nil {
		return nil, errx.New(errx.BadRequest, "unknown plan duration")
	}
	return id, nil
}

func (s *adminService) CreatePlan(ctx context.Context, adminID uuid.UUID, req *models.CreatePlanRequest, ipAddress, userAgent string) (*models.Plan, *errx.Error) {
	plan := &models.Plan{
		ID:                 uuid.New(),
		Name:               &req.Name,
		MaxContacts:        req.MaxContacts,
		DailyEmails:        req.DailyEmails,
		AIGeneration:       req.AIGeneration,
		AccountLimit:       req.AccountLimit,
		Price:              req.Price,
		DiscountedPrice:    req.DiscountedPrice,
		Duration:           req.Duration,
		Public:             req.Public,
		DedicatedWorkers:   req.DedicatedWorkers,
		DailyCampaignLimit: req.DailyCampaignLimit,
		MaxCampaigns:       req.MaxCampaigns,
		MaxActiveCampaigns: req.MaxActiveCampaigns,
		MaxTeamMembers:     req.MaxTeamMembers,
		MaxEmailAccounts:   req.MaxEmailAccounts,
	}

	durationID, xerr := s.resolveDuration(ctx, plan.Duration)
	if xerr != nil {
		return nil, xerr
	}

	if err := s.repo.CreatePlan(ctx, plan, *durationID); err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to create plan")
	}

	s.logAction(ctx, adminID, "create_plan", "plan", plan.ID, map[string]any{"name": req.Name}, ipAddress, userAgent)
	return plan, nil
}

func (s *adminService) GetPlan(ctx context.Context, planID uuid.UUID) (*models.Plan, *errx.Error) {
	plan, err := s.repo.GetPlan(ctx, planID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get plan")
	}
	if plan == nil {
		return nil, errx.ErrNotFound
	}
	return plan, nil
}

func (s *adminService) UpdatePlan(ctx context.Context, adminID, planID uuid.UUID, req *models.UpdatePlanRequest, ipAddress, userAgent string) (*models.Plan, *errx.Error) {
	plan, err := s.repo.GetPlan(ctx, planID)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get plan")
	}
	if plan == nil {
		return nil, errx.ErrNotFound
	}

	// Apply updates
	if req.Name != nil {
		plan.Name = req.Name
	}
	if req.MaxContacts != nil {
		plan.MaxContacts = *req.MaxContacts
	}
	if req.DailyEmails != nil {
		plan.DailyEmails = *req.DailyEmails
	}
	if req.AIGeneration != nil {
		plan.AIGeneration = *req.AIGeneration
	}
	if req.AccountLimit != nil {
		plan.AccountLimit = *req.AccountLimit
	}
	if req.Price != nil {
		plan.Price = *req.Price
	}
	if req.DiscountedPrice != nil {
		plan.DiscountedPrice = *req.DiscountedPrice
	}
	if req.Duration != nil {
		plan.Duration = *req.Duration
	}
	if req.Public != nil {
		plan.Public = *req.Public
	}
	if req.DedicatedWorkers != nil {
		plan.DedicatedWorkers = *req.DedicatedWorkers
	}
	if req.DailyCampaignLimit != nil {
		plan.DailyCampaignLimit = req.DailyCampaignLimit
	}
	if req.MaxCampaigns != nil {
		plan.MaxCampaigns = req.MaxCampaigns
	}
	if req.MaxActiveCampaigns != nil {
		plan.MaxActiveCampaigns = req.MaxActiveCampaigns
	}
	if req.MaxTeamMembers != nil {
		plan.MaxTeamMembers = req.MaxTeamMembers
	}
	if req.MaxEmailAccounts != nil {
		plan.MaxEmailAccounts = req.MaxEmailAccounts
	}

	durationID, xerr := s.resolveDuration(ctx, plan.Duration)
	if xerr != nil {
		return nil, xerr
	}

	if err := s.repo.UpdatePlan(ctx, plan, *durationID); err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to update plan")
	}

	s.logAction(ctx, adminID, "update_plan", "plan", planID, nil, ipAddress, userAgent)
	return plan, nil
}

func (s *adminService) DeletePlan(ctx context.Context, adminID, planID uuid.UUID, ipAddress, userAgent string) *errx.Error {
	// Check if plan is in use
	inUse, err := s.repo.IsPlanInUse(ctx, planID)
	if err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to check plan usage")
	}
	if inUse {
		return errx.New(errx.BadRequest, "cannot delete plan that is in use")
	}

	if err := s.repo.DeletePlan(ctx, planID); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to delete plan")
	}

	s.logAction(ctx, adminID, "delete_plan", "plan", planID, nil, ipAddress, userAgent)
	return nil
}

// Enterprise Inquiries

func (s *adminService) ListEnterpriseInquiries(ctx context.Context, search *models.AdminEnterpriseInquirySearch) (*models.AdminEnterpriseInquiriesResult, *errx.Error) {
	result, err := s.repo.ListEnterpriseInquiries(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list enterprise inquiries")
	}
	return result, nil
}

func (s *adminService) GetEnterpriseInquiry(ctx context.Context, id uuid.UUID) (*models.AdminEnterpriseInquiry, *errx.Error) {
	inquiry, err := s.repo.GetEnterpriseInquiry(ctx, id)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to get enterprise inquiry")
	}
	if inquiry == nil {
		return nil, errx.ErrNotFound
	}
	return inquiry, nil
}

func (s *adminService) UpdateEnterpriseInquiry(ctx context.Context, adminID, inquiryID uuid.UUID, update *models.UpdateEnterpriseInquiryRequest, ipAddress, userAgent string) *errx.Error {
	if err := s.repo.UpdateEnterpriseInquiry(ctx, inquiryID, update); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to update enterprise inquiry")
	}

	s.logAction(ctx, adminID, "update_enterprise_inquiry", "enterprise_inquiry", inquiryID, map[string]any{"update": update}, ipAddress, userAgent)
	return nil
}

// Admin Management

func (s *adminService) ListAdmins(ctx context.Context, cursor *uuid.UUID, limit int) (*models.AdminsResult, *errx.Error) {
	result, err := s.repo.ListAdmins(ctx, cursor, limit)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to list admins")
	}
	return result, nil
}

func (s *adminService) GrantAdminPermissions(ctx context.Context, adminID, targetUserID uuid.UUID, permissions models.AdminPermission, ipAddress, userAgent string) *errx.Error {
	// Cannot modify own permissions
	if adminID == targetUserID {
		return errx.New(errx.BadRequest, "cannot modify your own permissions")
	}

	if err := s.repo.UpdateUserAdminPermissions(ctx, targetUserID, uint32(permissions), adminID); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to grant admin permissions")
	}

	s.logAction(ctx, adminID, "grant_admin", "user", targetUserID, map[string]any{"permissions": permissions}, ipAddress, userAgent)
	return nil
}

func (s *adminService) RevokeAdminPermissions(ctx context.Context, adminID, targetUserID uuid.UUID, ipAddress, userAgent string) *errx.Error {
	// Cannot modify own permissions
	if adminID == targetUserID {
		return errx.New(errx.BadRequest, "cannot modify your own permissions")
	}

	if err := s.repo.UpdateUserAdminPermissions(ctx, targetUserID, 0, adminID); err != nil {
		sentry.CaptureException(err)
		return errx.New(errx.Internal, "failed to revoke admin permissions")
	}

	s.logAction(ctx, adminID, "revoke_admin", "user", targetUserID, nil, ipAddress, userAgent)
	return nil
}

// Audit Logs

func (s *adminService) SearchAuditLogs(ctx context.Context, search *models.AdminAuditLogSearch) (*models.AdminAuditLogsResult, *errx.Error) {
	result, err := s.repo.SearchAuditLogs(ctx, search)
	if err != nil {
		sentry.CaptureException(err)
		return nil, errx.New(errx.Internal, "failed to search audit logs")
	}
	return result, nil
}
