/**
 * Subscription Access Control Utilities
 *
 * Provides a 4-tier access level system for checking user access based on
 * subscription status, payment failures, and trial status.
 *
 * Access Levels:
 * - FULL: All features available
 * - LIMITED: Read + limited writes (no bulk operations, exports limited)
 * - READ_ONLY: View data only
 * - BLOCKED: No access (redirect to billing)
 *
 * @module utils/subscription-access
 */

import { subscriptionsAdapter } from '@/lib/adapters/subscriptions';
import { logger } from '@/lib/logger';
import { stripe } from '@/lib/stripe';

/**
 * Access level types
 */
export type AccessLevel = 'FULL' | 'LIMITED' | 'READ_ONLY' | 'BLOCKED';

/**
 * Feature names that can be checked for access
 */
export type FeatureName =
  | 'dashboard'
  | 'appointments'
  | 'settings'
  | 'billing'
  | 'export_data'
  | 'bulk_operations'
  | 'ai_assistant'
  | 'workflow_creation'
  | 'api_access';

/**
 * Access check result returned by checkUserAccess
 */
export interface AccessCheckResult {
  accessLevel: AccessLevel;
  canAccessFeature: (feature: FeatureName) => boolean;
  reason?: string;
  subscriptionStatus?: string;
  daysUntilExpiry?: number;
  paymentFailureCount?: number;
}

/**
 * Feature access matrix defining which features are available at each access level
 */
const FEATURE_ACCESS_MATRIX: Record<AccessLevel, Set<FeatureName>> = {
  FULL: new Set<FeatureName>([
    'dashboard',
    'appointments',
    'settings',
    'billing',
    'export_data',
    'bulk_operations',
    'ai_assistant',
    'workflow_creation',
    'api_access',
  ]),
  LIMITED: new Set<FeatureName>([
    'dashboard',
    'appointments',
    'settings',
    'billing',
    // No bulk_operations, limited exports (handled in business logic)
  ]),
  READ_ONLY: new Set<FeatureName>([
    'dashboard',
    'appointments',
    'billing',
    // View only - no modifications
  ]),
  BLOCKED: new Set<FeatureName>([
    // No features - redirect to billing
  ]),
};

/**
 * Check if a specific feature is accessible at a given access level
 *
 * @param accessLevel - The user's current access level
 * @param feature - The feature to check access for
 * @returns boolean indicating if the feature is accessible
 */
export function canAccessFeature(accessLevel: AccessLevel, feature: FeatureName): boolean {
  const allowedFeatures = FEATURE_ACCESS_MATRIX[accessLevel];
  return allowedFeatures.has(feature);
}

/**
 * Calculate days until a date
 *
 * @param date - Target date
 * @returns Number of days until the date (negative if in the past)
 */
function calculateDaysUntil(date: Date): number {
  const now = new Date();
  const diffTime = date.getTime() - now.getTime();
  return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}

/**
 * Check user access based on subscription status
 *
 * Access level determination:
 * - 'active' or 'trialing' -> FULL access
 * - 'past_due' with 1-2 failures -> LIMITED access
 * - 'past_due' with 3+ failures or 'suspended' -> READ_ONLY access
 * - 'cancelled', 'incomplete', or no subscription -> BLOCKED access
 *
 * @param userId - The user ID to check access for
 * @returns AccessCheckResult with access level and feature access function
 */
export async function checkUserAccess(userId: string): Promise<AccessCheckResult> {
  try {
    // Find the most recent subscription for the user
    const subscription = await subscriptionsAdapter.findFirst({
      where: { userId },
      orderBy: { createdAt: 'desc' },
      select: {
        status: true,
        paymentFailureCount: true,
        currentPeriodEnd: true,
        trialEndsAt: true,
        cancelledAt: true,
        stripeCustomerId: true,
      },
    }) as {
      status: string;
      paymentFailureCount: number | null;
      currentPeriodEnd: Date | null;
      trialEndsAt: Date | null;
      cancelledAt: Date | null;
      stripeCustomerId: string;
    } | null;

    // No subscription found
    if (!subscription) {
      logger.info('No subscription found for user', { userId });
      return createAccessResult('BLOCKED', {
        reason: 'No active subscription found',
        subscriptionStatus: 'none',
      });
    }

    const { status, paymentFailureCount, currentPeriodEnd, trialEndsAt } = subscription;
    const failureCount = paymentFailureCount ?? 0;

    // Calculate days until expiry
    let daysUntilExpiry: number | undefined;
    if (trialEndsAt) {
      daysUntilExpiry = calculateDaysUntil(new Date(trialEndsAt));
    } else if (currentPeriodEnd) {
      daysUntilExpiry = calculateDaysUntil(new Date(currentPeriodEnd));
    }

    // FULL access: active or trialing
    if (status === 'active' || status === 'trialing') {
      return createAccessResult('FULL', {
        subscriptionStatus: status,
        daysUntilExpiry,
        paymentFailureCount: failureCount,
      });
    }

    // LIMITED access: past_due with 1-2 failures
    if (status === 'past_due' && failureCount < 3) {
      return createAccessResult('LIMITED', {
        reason: `Payment past due. ${failureCount} failed payment attempt(s). Please update your payment method.`,
        subscriptionStatus: status,
        daysUntilExpiry,
        paymentFailureCount: failureCount,
      });
    }

    // READ_ONLY access: past_due with 3+ failures or suspended
    if ((status === 'past_due' && failureCount >= 3) || status === 'suspended') {
      return createAccessResult('READ_ONLY', {
        reason: `Account suspended after ${failureCount} failed payment attempts. Update your payment method to restore access.`,
        subscriptionStatus: status,
        daysUntilExpiry,
        paymentFailureCount: failureCount,
      });
    }

    // BLOCKED access: cancelled, incomplete, or other statuses
    if (status === 'cancelled' || status === 'incomplete' || status === 'incomplete_expired') {
      return createAccessResult('BLOCKED', {
        reason: status === 'cancelled'
          ? 'Subscription has been cancelled. Please subscribe to regain access.'
          : 'Subscription setup incomplete. Please complete payment.',
        subscriptionStatus: status,
        paymentFailureCount: failureCount,
      });
    }

    // Default to BLOCKED for unknown statuses
    logger.warn('Unknown subscription status', { userId, status });
    return createAccessResult('BLOCKED', {
      reason: 'Unable to verify subscription status',
      subscriptionStatus: status,
    });
  } catch (error) {
    logger.error('Failed to check user access', error, { userId });

    // Fail open with LIMITED access to avoid blocking users due to errors
    return createAccessResult('LIMITED', {
      reason: 'Error checking subscription status. Please try again.',
      subscriptionStatus: 'error',
    });
  }
}

/**
 * Helper function to create an AccessCheckResult
 */
function createAccessResult(
  accessLevel: AccessLevel,
  options: {
    reason?: string;
    subscriptionStatus?: string;
    daysUntilExpiry?: number;
    paymentFailureCount?: number;
  } = {}
): AccessCheckResult {
  return {
    accessLevel,
    canAccessFeature: (feature: FeatureName) => canAccessFeature(accessLevel, feature),
    ...options,
  };
}

/**
 * Check if a subscription is at risk of suspension
 *
 * Returns true if:
 * - Subscription has 2+ failed payments
 * - Trial ending within 3 days without a payment method attached
 *
 * @param userId - The user ID to check
 * @returns boolean indicating if subscription is at risk
 */
export async function isSubscriptionAtRisk(userId: string): Promise<boolean> {
  try {
    const subscription = await subscriptionsAdapter.findFirst({
      where: { userId },
      orderBy: { createdAt: 'desc' },
      select: {
        status: true,
        paymentFailureCount: true,
        trialEndsAt: true,
        stripeCustomerId: true,
      },
    }) as {
      status: string;
      paymentFailureCount: number | null;
      trialEndsAt: Date | null;
      stripeCustomerId: string | null;
    } | null;

    if (!subscription) {
      return false;
    }

    // Check for 2+ failed payments
    const failureCount = subscription.paymentFailureCount ?? 0;
    if (failureCount >= 2) {
      logger.info('Subscription at risk due to payment failures', {
        userId,
        failureCount,
      });
      return true;
    }

    // Check for trial ending within 3 days without payment method
    if (subscription.status === 'trialing' && subscription.trialEndsAt) {
      const daysUntilTrialEnd = calculateDaysUntil(new Date(subscription.trialEndsAt));

      if (daysUntilTrialEnd <= 3 && daysUntilTrialEnd >= 0) {
        // Check if customer has a payment method
        if (subscription.stripeCustomerId) {
          const hasPayment = await hasPaymentMethod(subscription.stripeCustomerId);
          if (!hasPayment) {
            logger.info('Subscription at risk - trial ending without payment method', {
              userId,
              daysUntilTrialEnd,
            });
            return true;
          }
        } else {
          // No Stripe customer ID means no payment method
          logger.info('Subscription at risk - trial ending, no Stripe customer', {
            userId,
            daysUntilTrialEnd,
          });
          return true;
        }
      }
    }

    return false;
  } catch (error) {
    logger.error('Failed to check subscription risk', error, { userId });
    return false;
  }
}

/**
 * Check if a Stripe customer has a valid payment method attached
 *
 * @param customerId - The Stripe customer ID
 * @returns boolean indicating if customer has a payment method
 */
export async function hasPaymentMethod(customerId: string): Promise<boolean> {
  try {
    // List payment methods for the customer
    const paymentMethods = await stripe.paymentMethods.list({
      customer: customerId,
      type: 'card',
      limit: 1,
    });

    if (paymentMethods.data.length > 0) {
      return true;
    }

    // Also check for other payment method types
    const bankMethods = await stripe.paymentMethods.list({
      customer: customerId,
      type: 'us_bank_account',
      limit: 1,
    });

    if (bankMethods.data.length > 0) {
      return true;
    }

    // Check SEPA debit for European customers
    const sepaMethods = await stripe.paymentMethods.list({
      customer: customerId,
      type: 'sepa_debit',
      limit: 1,
    });

    return sepaMethods.data.length > 0;
  } catch (error) {
    logger.error('Failed to check payment methods', error, { customerId });
    return false;
  }
}

/**
 * Get a human-readable message for an access level
 *
 * @param accessResult - The access check result
 * @returns User-friendly message describing the access level
 */
export function getAccessMessage(accessResult: AccessCheckResult): string {
  if (accessResult.reason) {
    return accessResult.reason;
  }

  switch (accessResult.accessLevel) {
    case 'FULL':
      return 'You have full access to all features.';
    case 'LIMITED':
      return 'Your access is limited. Some features may be unavailable.';
    case 'READ_ONLY':
      return 'Your account is in read-only mode. Update your payment to restore full access.';
    case 'BLOCKED':
      return 'Your account access has been blocked. Please update your subscription.';
    default:
      return 'Unknown access level.';
  }
}

/**
 * Check if user needs to be redirected to billing page
 *
 * @param accessLevel - The user's access level
 * @returns boolean indicating if redirect to billing is needed
 */
export function shouldRedirectToBilling(accessLevel: AccessLevel): boolean {
  return accessLevel === 'BLOCKED';
}

/**
 * Get the list of restricted features for an access level
 *
 * @param accessLevel - The access level to check
 * @returns Array of feature names that are restricted
 */
export function getRestrictedFeatures(accessLevel: AccessLevel): FeatureName[] {
  const allFeatures: FeatureName[] = [
    'dashboard',
    'appointments',
    'settings',
    'billing',
    'export_data',
    'bulk_operations',
    'ai_assistant',
    'workflow_creation',
    'api_access',
  ];

  const allowedFeatures = FEATURE_ACCESS_MATRIX[accessLevel];
  return allFeatures.filter((feature) => !allowedFeatures.has(feature));
}

/**
 * Legacy enum for backward compatibility
 * @deprecated Use AccessLevel type instead
 */
export enum SubscriptionStatus {
  ACTIVE = 'active',
  TRIALING = 'trialing',
  PAST_DUE = 'past_due',
  SUSPENDED = 'suspended',
  CANCELLED = 'cancelled',
  INCOMPLETE = 'incomplete',
  INCOMPLETE_EXPIRED = 'incomplete_expired',
  PENDING_VERIFICATION = 'pending_verification',
}

/**
 * Legacy enum for backward compatibility
 * @deprecated Use AccessLevel type instead
 */
export enum AccessLevelEnum {
  FULL = 'full',
  LIMITED = 'limited',
  READ_ONLY = 'read_only',
  BLOCKED = 'blocked',
}
