/**
 * Subscription Access Control Service
 *
 * Provides granular access control based on subscription status.
 * Handles all subscription states: active, trialing, past_due, suspended, cancelled, incomplete
 *
 * Key features:
 * - checkUserAccess() - Determines user access level based on subscription status
 * - isSubscriptionAtRisk() - Proactive risk detection (2+ failed payments)
 * - hasPaymentMethod() - Validates payment method before trial ends
 * - canAccessFeature() - Granular feature-level access control
 *
 * @module services/subscription-access
 */

import { prisma } from '@/lib/db';
import { subscriptionsAdapter } from '@/lib/adapters/subscriptions';
import { logger } from '@/lib/logger';
import type {
  AccessLevel,
  Feature,
  UserAccessResult,
  SubscriptionRiskResult,
  FeatureAccessResult,
  RiskFactor,
  SubscriptionForAccess,
  UserWithSubscription,
  SubscriptionStatus,
  AccessCheckOptions,
} from '@/lib/types/access-control.types';

// Re-export types for convenience
export type {
  AccessLevel,
  Feature,
  UserAccessResult,
  SubscriptionRiskResult,
  FeatureAccessResult,
  RiskFactor,
};

// Import constants
import {
  ACCESS_CONTROL_CONSTANTS as CONSTANTS,
  ACCESS_LEVEL_FEATURES as LEVEL_FEATURES,
  RESTRICTION_MESSAGES as MESSAGES,
} from '@/lib/types/access-control.types';

/**
 * Subscription Access Control Service
 */
export class SubscriptionAccessService {
  /**
   * Check user access level based on subscription status
   *
   * @param userId - The user ID to check
   * @param options - Optional configuration for the check
   * @returns UserAccessResult with access level and details
   */
  async checkUserAccess(
    userId: string,
    options: AccessCheckOptions = {}
  ): Promise<UserAccessResult> {
    try {
      // Fetch user with subscription and organization membership
      const user = await this.getUserWithSubscription(userId, options.includeOrgMembership);

      if (!user) {
        return this.createNoAccessResult('User not found');
      }

      // Check organization membership first (inherited subscription)
      if (options.includeOrgMembership && user.organizationMembership) {
        const orgOwnerAccess = await this.checkOrgOwnerSubscription(
          user.organizationMembership.organization.ownerId
        );
        if (orgOwnerAccess.hasAccess) {
          return {
            ...orgOwnerAccess,
            restrictionReason: undefined, // Clear any restriction from org check
          };
        }
      }

      // No subscription at all
      if (!user.subscription) {
        return this.createNoAccessResult('No subscription found', MESSAGES.no_subscription);
      }

      const subscription = user.subscription;
      const now = new Date();

      // Determine access based on subscription status
      return this.determineAccessLevel(subscription, now);
    } catch (error) {
      logger.error('Error checking user access', error, { userId });
      // Fail secure - deny access on error
      return this.createNoAccessResult('Error checking access');
    }
  }

  /**
   * Check if a subscription is at risk (payment issues, trial ending, etc.)
   *
   * @param userId - The user ID to check
   * @returns SubscriptionRiskResult with risk assessment
   */
  async isSubscriptionAtRisk(userId: string): Promise<SubscriptionRiskResult> {
    try {
      const user = await this.getUserWithSubscription(userId);

      if (!user?.subscription) {
        return {
          isAtRisk: false,
          riskLevel: 0,
          riskFactors: [],
          recommendations: [],
        };
      }

      const subscription = user.subscription;
      const now = new Date();
      const riskFactors: RiskFactor[] = [];
      const recommendations: string[] = [];

      // Check for payment failures
      if (subscription.paymentFailureCount >= CONSTANTS.AT_RISK_FAILURE_COUNT) {
        riskFactors.push({
          type: 'multiple_failures',
          severity: subscription.paymentFailureCount >= 3 ? 'critical' : 'high',
          description: `${subscription.paymentFailureCount} payment attempt(s) have failed`,
          detectedAt: subscription.lastPaymentAttempt || undefined,
          metadata: { failureCount: subscription.paymentFailureCount },
        });
        recommendations.push('Update your payment method immediately');
      } else if (subscription.paymentFailureCount === 1) {
        riskFactors.push({
          type: 'payment_failed',
          severity: 'medium',
          description: 'Last payment attempt failed',
          detectedAt: subscription.lastPaymentAttempt || undefined,
        });
        recommendations.push('Check your payment method and ensure sufficient funds');
      }

      // Check for missing payment method
      if (!subscription.paymentMethodId) {
        riskFactors.push({
          type: 'no_payment_method',
          severity: subscription.status === 'trialing' ? 'high' : 'medium',
          description: 'No payment method on file',
        });
        recommendations.push('Add a payment method to avoid service interruption');
      }

      // Check for trial ending soon
      if (subscription.status === 'trialing' && subscription.trialEndsAt) {
        const daysUntilTrialEnd = Math.ceil(
          (subscription.trialEndsAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
        );

        if (daysUntilTrialEnd <= CONSTANTS.TRIAL_ENDING_SOON_DAYS && daysUntilTrialEnd > 0) {
          riskFactors.push({
            type: 'trial_ending',
            severity: daysUntilTrialEnd <= 1 ? 'high' : 'medium',
            description: `Trial ends in ${daysUntilTrialEnd} day(s)`,
            metadata: { daysRemaining: daysUntilTrialEnd, trialEndsAt: subscription.trialEndsAt },
          });

          if (!subscription.paymentMethodId) {
            recommendations.push(`Add a payment method within ${daysUntilTrialEnd} day(s) to continue service`);
          }
        }
      }

      // Check for past due status
      if (subscription.status === 'past_due') {
        riskFactors.push({
          type: 'past_due',
          severity: 'high',
          description: 'Subscription payment is past due',
        });
        recommendations.push('Resolve payment issues to restore full access');
      }

      // Check for pending cancellation
      if (subscription.cancelAtPeriodEnd) {
        riskFactors.push({
          type: 'cancellation_pending',
          severity: 'low',
          description: 'Subscription will cancel at period end',
          metadata: { cancelDate: subscription.currentPeriodEnd },
        });
        recommendations.push('Reactivate subscription to maintain access');
      }

      // Calculate risk level
      let riskLevel: 0 | 1 | 2 | 3 = 0;
      if (riskFactors.some((f) => f.severity === 'critical')) {
        riskLevel = 3;
      } else if (riskFactors.some((f) => f.severity === 'high')) {
        riskLevel = 2;
      } else if (riskFactors.some((f) => f.severity === 'medium')) {
        riskLevel = 1;
      }

      return {
        isAtRisk: riskLevel > 0,
        riskLevel,
        riskFactors,
        recommendations,
      };
    } catch (error) {
      logger.error('Error checking subscription risk', error, { userId });
      return {
        isAtRisk: false,
        riskLevel: 0,
        riskFactors: [],
        recommendations: [],
      };
    }
  }

  /**
   * Check if user has a valid payment method on file
   *
   * @param userId - The user ID to check
   * @returns boolean indicating if payment method exists
   */
  async hasPaymentMethod(userId: string): Promise<boolean> {
    try {
      const subscription = await subscriptionsAdapter.findFirst({
        where: { userId },
        select: { paymentMethodId: true },
        orderBy: { createdAt: 'desc' },
      });

      return !!subscription?.paymentMethodId;
    } catch (error) {
      logger.error('Error checking payment method', error, { userId });
      return false;
    }
  }

  /**
   * Check if user has a valid payment method by Stripe subscription ID
   *
   * @param stripeSubscriptionId - The Stripe subscription ID
   * @returns boolean indicating if payment method exists
   */
  async hasPaymentMethodByStripeId(stripeSubscriptionId: string): Promise<boolean> {
    try {
      const subscription = await subscriptionsAdapter.findUnique({
        where: { stripeSubscriptionId },
        select: { paymentMethodId: true },
      });

      return !!subscription?.paymentMethodId;
    } catch (error) {
      logger.error('Error checking payment method by Stripe ID', error, { stripeSubscriptionId });
      return false;
    }
  }

  /**
   * Check if user can access a specific feature
   *
   * @param userId - The user ID to check
   * @param feature - The feature to check access for
   * @param options - Optional configuration
   * @returns FeatureAccessResult with access details
   */
  async canAccessFeature(
    userId: string,
    feature: Feature,
    options: AccessCheckOptions = {}
  ): Promise<FeatureAccessResult> {
    try {
      // Always allow billing and settings access
      if (CONSTANTS.ALWAYS_ACCESSIBLE_FEATURES.includes(feature)) {
        return {
          canAccess: true,
          accessLevel: 'full',
          readOnly: false,
        };
      }

      const accessResult = await this.checkUserAccess(userId, options);

      // Check if feature is accessible at current access level
      const allowedFeatures = LEVEL_FEATURES[accessResult.accessLevel];
      const canAccess = allowedFeatures.includes(feature);

      // Determine if read-only access
      const readOnly =
        accessResult.accessLevel === 'read_only' ||
        (accessResult.accessLevel === 'limited' && CONSTANTS.READ_ONLY_FEATURES.includes(feature));

      // Generate appropriate message
      let message: string | undefined;
      let upgradeRequired = false;

      if (!canAccess) {
        if (!accessResult.hasAccess) {
          message = accessResult.restrictionReason || 'Access denied';
        } else {
          message = 'This feature is not available at your current access level';
          upgradeRequired = true;
        }
      } else if (readOnly) {
        message = 'This feature is in read-only mode due to payment issues';
      }

      return {
        canAccess,
        accessLevel: accessResult.accessLevel,
        reason: canAccess ? undefined : message,
        readOnly,
        upgradeRequired,
        message,
      };
    } catch (error) {
      logger.error('Error checking feature access', error, { userId, feature });
      // Fail secure - deny access on error
      return {
        canAccess: false,
        accessLevel: 'none',
        reason: 'Error checking access',
        readOnly: true,
      };
    }
  }

  /**
   * Get access summary for a user (useful for UI display)
   *
   * @param userId - The user ID
   * @returns Object with access details for display
   */
  async getAccessSummary(userId: string): Promise<{
    accessLevel: AccessLevel;
    isTrialing: boolean;
    trialDaysRemaining: number | null;
    isAtRisk: boolean;
    riskLevel: 0 | 1 | 2 | 3;
    needsAction: boolean;
    actionMessage: string | null;
    actionUrl: string | null;
  }> {
    const [accessResult, riskResult] = await Promise.all([
      this.checkUserAccess(userId, { includeOrgMembership: true }),
      this.isSubscriptionAtRisk(userId),
    ]);

    return {
      accessLevel: accessResult.accessLevel,
      isTrialing: accessResult.isTrialing,
      trialDaysRemaining: accessResult.trialDaysRemaining,
      isAtRisk: riskResult.isAtRisk,
      riskLevel: riskResult.riskLevel,
      needsAction: riskResult.isAtRisk || !accessResult.hasAccess,
      actionMessage: accessResult.suggestedAction || riskResult.recommendations[0] || null,
      actionUrl: accessResult.actionUrl || null,
    };
  }

  // ============================================
  // PRIVATE HELPER METHODS
  // ============================================

  /**
   * Fetch user with subscription data
   */
  private async getUserWithSubscription(
    userId: string,
    includeOrgMembership = false
  ): Promise<UserWithSubscription | null> {
    const user = await prisma.users.findUnique({
      where: { id: userId },
      select: {
        id: true,
        email: true,
        name: true,
        subscriptions: {
          where: {
            status: { not: 'incomplete_expired' },
          },
          orderBy: { createdAt: 'desc' },
          take: 1,
          select: {
            id: true,
            status: true,
            tierId: true,
            trialEndsAt: true,
            currentPeriodEnd: true,
            cancelAtPeriodEnd: true,
            paymentFailureCount: true,
            paymentMethodId: true,
            lastPaymentAttempt: true,
            lastPaymentDate: true,
          },
        },
        ...(includeOrgMembership
          ? {
              organization_members: {
                take: 1,
                select: {
                  organizationId: true,
                  role: true,
                  organizations: {
                    select: {
                      id: true,
                      name: true,
                      ownerId: true,
                    },
                  },
                },
              },
            }
          : {}),
      },
    });

    if (!user) {
      return null;
    }

    const subscription = user.subscriptions[0] || null;

    return {
      id: user.id,
      email: user.email,
      name: user.name,
      subscription: subscription
        ? {
            id: subscription.id,
            status: subscription.status,
            tierId: subscription.tierId,
            trialEndsAt: subscription.trialEndsAt,
            currentPeriodEnd: subscription.currentPeriodEnd,
            cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
            paymentFailureCount: subscription.paymentFailureCount,
            paymentMethodId: subscription.paymentMethodId,
            lastPaymentAttempt: subscription.lastPaymentAttempt,
            lastPaymentDate: subscription.lastPaymentDate,
          }
        : null,
      organizationMembership:
        includeOrgMembership && 'organization_members' in user
          ? (() => {
              // Cast to access the dynamically included relation
              type OrgMemberWithRelation = {
                organizationId: string;
                role: string;
                organizations: {
                  id: string;
                  name: string;
                  ownerId: string;
                };
              };
              const orgMembers = (user as unknown as { organization_members?: OrgMemberWithRelation[] })
                .organization_members;
              const member = orgMembers?.[0];
              return member
                ? {
                    organizationId: member.organizationId,
                    role: member.role,
                    organization: {
                      id: member.organizations.id,
                      name: member.organizations.name,
                      ownerId: member.organizations.ownerId,
                    },
                  }
                : null;
            })()
          : null,
    };
  }

  /**
   * Check organization owner's subscription for inherited access
   */
  private async checkOrgOwnerSubscription(ownerId: string): Promise<UserAccessResult> {
    return this.checkUserAccess(ownerId, { includeOrgMembership: false });
  }

  /**
   * Determine access level based on subscription status
   */
  private determineAccessLevel(subscription: SubscriptionForAccess, now: Date): UserAccessResult {
    const status = subscription.status as SubscriptionStatus;

    // Calculate trial days remaining
    let trialDaysRemaining: number | null = null;
    if (subscription.trialEndsAt) {
      trialDaysRemaining = Math.ceil(
        (subscription.trialEndsAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
      );
      if (trialDaysRemaining < 0) trialDaysRemaining = 0;
    }

    const baseResult: Omit<UserAccessResult, 'hasAccess' | 'accessLevel'> = {
      subscriptionStatus: status,
      isTrialing: status === 'trialing',
      trialEndsAt: subscription.trialEndsAt,
      trialDaysRemaining,
      isAtRisk: subscription.paymentFailureCount >= CONSTANTS.AT_RISK_FAILURE_COUNT,
      paymentFailureCount: subscription.paymentFailureCount,
      hasPaymentMethod: !!subscription.paymentMethodId,
      currentPeriodEnd: subscription.currentPeriodEnd,
      cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
    };

    switch (status) {
      case 'active':
        // Check if at risk due to payment failures
        if (subscription.paymentFailureCount >= CONSTANTS.AT_RISK_FAILURE_COUNT) {
          return {
            ...baseResult,
            hasAccess: true,
            accessLevel: 'limited',
            restrictionReason: MESSAGES.payment_failed.message,
            suggestedAction: MESSAGES.payment_failed.action,
            actionUrl: MESSAGES.payment_failed.url,
          };
        }
        return {
          ...baseResult,
          hasAccess: true,
          accessLevel: 'full',
        };

      case 'trialing':
        // Check if trial has expired
        if (subscription.trialEndsAt && subscription.trialEndsAt < now) {
          // Trial expired without payment method
          if (!subscription.paymentMethodId) {
            return {
              ...baseResult,
              hasAccess: false,
              accessLevel: 'none',
              restrictionReason: MESSAGES.trial_expired.message,
              suggestedAction: MESSAGES.trial_expired.action,
              actionUrl: MESSAGES.trial_expired.url,
            };
          }
        }

        // Trial active but no payment method - warn user
        if (!subscription.paymentMethodId && trialDaysRemaining !== null && trialDaysRemaining <= 3) {
          return {
            ...baseResult,
            hasAccess: true,
            accessLevel: 'trial',
            restrictionReason: MESSAGES.trial_ending.message,
            suggestedAction: MESSAGES.trial_ending.action,
            actionUrl: MESSAGES.trial_ending.url,
          };
        }

        return {
          ...baseResult,
          hasAccess: true,
          accessLevel: 'trial',
        };

      case 'past_due':
        // Limited access during grace period
        return {
          ...baseResult,
          hasAccess: true,
          accessLevel: 'limited',
          restrictionReason: MESSAGES.payment_failed.message,
          suggestedAction: MESSAGES.payment_failed.action,
          actionUrl: MESSAGES.payment_failed.url,
        };

      case 'suspended':
      case 'unpaid':
        // Read-only access - can view but not modify
        return {
          ...baseResult,
          hasAccess: true,
          accessLevel: 'read_only',
          restrictionReason: MESSAGES.suspended.message,
          suggestedAction: MESSAGES.suspended.action,
          actionUrl: MESSAGES.suspended.url,
        };

      case 'cancelled':
        // Check if still within period
        if (subscription.currentPeriodEnd > now) {
          return {
            ...baseResult,
            hasAccess: true,
            accessLevel: 'limited',
            restrictionReason: MESSAGES.cancelled.message,
            suggestedAction: MESSAGES.cancelled.action,
            actionUrl: MESSAGES.cancelled.url,
          };
        }
        // Period ended
        return {
          ...baseResult,
          hasAccess: false,
          accessLevel: 'none',
          restrictionReason: MESSAGES.cancelled.message,
          suggestedAction: MESSAGES.cancelled.action,
          actionUrl: MESSAGES.cancelled.url,
        };

      case 'incomplete':
        // Waiting for payment confirmation
        return {
          ...baseResult,
          hasAccess: true,
          accessLevel: 'limited',
          restrictionReason: 'Payment confirmation pending',
          suggestedAction: 'Complete Payment',
          actionUrl: '/en/dashboard?tab=billing',
        };

      case 'incomplete_expired':
        // Payment never completed
        return {
          ...baseResult,
          hasAccess: false,
          accessLevel: 'none',
          restrictionReason: 'Payment was not completed in time',
          suggestedAction: 'Subscribe Now',
          actionUrl: '/en/pricing',
        };

      case 'paused':
        // Subscription paused
        return {
          ...baseResult,
          hasAccess: true,
          accessLevel: 'read_only',
          restrictionReason: 'Subscription is paused',
          suggestedAction: 'Resume Subscription',
          actionUrl: '/en/dashboard?tab=billing',
        };

      default:
        // Unknown status - fail secure
        return {
          ...baseResult,
          hasAccess: false,
          accessLevel: 'none',
          restrictionReason: 'Unknown subscription status',
        };
    }
  }

  /**
   * Create a no-access result
   */
  private createNoAccessResult(
    reason: string,
    messages?: (typeof MESSAGES)[keyof typeof MESSAGES]
  ): UserAccessResult {
    return {
      hasAccess: false,
      accessLevel: 'none',
      subscriptionStatus: null,
      isTrialing: false,
      trialEndsAt: null,
      trialDaysRemaining: null,
      isAtRisk: false,
      paymentFailureCount: 0,
      hasPaymentMethod: false,
      currentPeriodEnd: null,
      cancelAtPeriodEnd: false,
      restrictionReason: messages?.message || reason,
      suggestedAction: messages?.action,
      actionUrl: messages?.url,
    };
  }
}

// ============================================
// SINGLETON EXPORT
// ============================================

export const subscriptionAccessService = new SubscriptionAccessService();
export default subscriptionAccessService;

// ============================================
// CONVENIENCE FUNCTIONS
// ============================================

/**
 * Check if user has access (convenience function)
 */
export async function checkUserAccess(
  userId: string,
  options?: AccessCheckOptions
): Promise<UserAccessResult> {
  return subscriptionAccessService.checkUserAccess(userId, options);
}

/**
 * Check if subscription is at risk (convenience function)
 */
export async function isSubscriptionAtRisk(userId: string): Promise<SubscriptionRiskResult> {
  return subscriptionAccessService.isSubscriptionAtRisk(userId);
}

/**
 * Check if user has payment method (convenience function)
 */
export async function hasPaymentMethod(userId: string): Promise<boolean> {
  return subscriptionAccessService.hasPaymentMethod(userId);
}

/**
 * Check if user can access feature (convenience function)
 */
export async function canAccessFeature(
  userId: string,
  feature: Feature,
  options?: AccessCheckOptions
): Promise<FeatureAccessResult> {
  return subscriptionAccessService.canAccessFeature(userId, feature, options);
}
