/**
 * Subscription Entity
 *
 * Represents an organization's subscription, including their plan,
 * Stripe integration, and billing period information.
 */

import { SubscriptionPlan, SubscriptionPlanType } from '../value-objects/subscription-plan.vo';
import {
  SubscriptionStatus,
  SubscriptionStatusType,
} from '../value-objects/subscription-status.vo';
import {
  InvalidSubscriptionDowngradeException,
  SubscriptionNotActiveException,
} from '../exceptions/subscription.exceptions';

export interface SubscriptionProps {
  readonly id: string;
  readonly organizationId: string;
  readonly plan: SubscriptionPlan;
  readonly status: SubscriptionStatus;
  readonly stripeCustomerId: string | null;
  readonly stripeSubscriptionId: string | null;
  readonly currentPeriodStart: Date | null;
  readonly currentPeriodEnd: Date | null;
  readonly cancelAtPeriodEnd: boolean;
  readonly createdAt: Date;
  readonly updatedAt: Date;
}

export class Subscription {
  private readonly props: SubscriptionProps;

  private constructor(props: SubscriptionProps) {
    this.props = props;
  }

  /**
   * Create a new subscription (defaults to Bronze/free plan)
   */
  static create(props: {
    id: string;
    organizationId: string;
    plan?: SubscriptionPlan;
    stripeCustomerId?: string | null;
    stripeSubscriptionId?: string | null;
  }): Subscription {
    const now = new Date();
    return new Subscription({
      id: props.id,
      organizationId: props.organizationId,
      plan: props.plan ?? SubscriptionPlan.bronze(),
      status: SubscriptionStatus.active(),
      stripeCustomerId: props.stripeCustomerId ?? null,
      stripeSubscriptionId: props.stripeSubscriptionId ?? null,
      currentPeriodStart: null,
      currentPeriodEnd: null,
      cancelAtPeriodEnd: false,
      createdAt: now,
      updatedAt: now,
    });
  }

  /**
   * Reconstitute from persistence
   */
  /**
   * Check if a specific plan feature is available
   */
  hasFeature(feature: import('../value-objects/plan-feature.vo').PlanFeature): boolean {
    return this.props.plan.hasFeature(feature);
  }

  /**
   * Get the maximum shipments per year allowed
   */
  get maxShipmentsPerYear(): number {
    return this.props.plan.maxShipmentsPerYear;
  }

  /**
   * Get the commission rate for this subscription's plan
   */
  get commissionRatePercent(): number {
    return this.props.plan.commissionRatePercent;
  }

  /**
   * Get the status badge for this subscription's plan
   */
  get statusBadge(): string {
    return this.props.plan.statusBadge;
  }

  /**
   * Reconstitute from persistence (supports legacy plan names)
   */
  static fromPersistence(props: {
    id: string;
    organizationId: string;
    plan: string; // Accepts both old and new plan names
    status: SubscriptionStatusType;
    stripeCustomerId: string | null;
    stripeSubscriptionId: string | null;
    currentPeriodStart: Date | null;
    currentPeriodEnd: Date | null;
    cancelAtPeriodEnd: boolean;
    createdAt: Date;
    updatedAt: Date;
  }): Subscription {
    return new Subscription({
      id: props.id,
      organizationId: props.organizationId,
      plan: SubscriptionPlan.fromString(props.plan),
      status: SubscriptionStatus.create(props.status),
      stripeCustomerId: props.stripeCustomerId,
      stripeSubscriptionId: props.stripeSubscriptionId,
      currentPeriodStart: props.currentPeriodStart,
      currentPeriodEnd: props.currentPeriodEnd,
      cancelAtPeriodEnd: props.cancelAtPeriodEnd,
      createdAt: props.createdAt,
      updatedAt: props.updatedAt,
    });
  }

  // Getters
  get id(): string {
    return this.props.id;
  }

  get organizationId(): string {
    return this.props.organizationId;
  }

  get plan(): SubscriptionPlan {
    return this.props.plan;
  }

  get status(): SubscriptionStatus {
    return this.props.status;
  }

  get stripeCustomerId(): string | null {
    return this.props.stripeCustomerId;
  }

  get stripeSubscriptionId(): string | null {
    return this.props.stripeSubscriptionId;
  }

  get currentPeriodStart(): Date | null {
    return this.props.currentPeriodStart;
  }

  get currentPeriodEnd(): Date | null {
    return this.props.currentPeriodEnd;
  }

  get cancelAtPeriodEnd(): boolean {
    return this.props.cancelAtPeriodEnd;
  }

  get createdAt(): Date {
    return this.props.createdAt;
  }

  get updatedAt(): Date {
    return this.props.updatedAt;
  }

  // Business logic

  /**
   * Get the maximum number of licenses allowed by this subscription
   */
  get maxLicenses(): number {
    return this.props.plan.maxLicenses;
  }

  /**
   * Check if the subscription has unlimited licenses
   */
  isUnlimited(): boolean {
    return this.props.plan.isUnlimited();
  }

  /**
   * Check if the subscription is active and allows access
   */
  isActive(): boolean {
    return this.props.status.allowsAccess();
  }

  /**
   * Check if the subscription is in good standing
   */
  isInGoodStanding(): boolean {
    return this.props.status.isInGoodStanding();
  }

  /**
   * Check if the subscription requires user action
   */
  requiresAction(): boolean {
    return this.props.status.requiresAction();
  }

  /**
   * Check if this is a free subscription
   */
  isFree(): boolean {
    return this.props.plan.isFree();
  }

  /**
   * Check if this is a paid subscription
   */
  isPaid(): boolean {
    return this.props.plan.isPaid();
  }

  /**
   * Check if the subscription is scheduled to be canceled
   */
  isScheduledForCancellation(): boolean {
    return this.props.cancelAtPeriodEnd;
  }

  /**
   * Check if a given number of licenses can be allocated
   */
  canAllocateLicenses(currentCount: number, additionalCount: number = 1): boolean {
    if (!this.isActive()) return false;
    if (this.isUnlimited()) return true;
    return currentCount + additionalCount <= this.maxLicenses;
  }

  /**
   * Check if upgrade to target plan is possible
   */
  canUpgradeTo(targetPlan: SubscriptionPlan): boolean {
    return this.props.plan.canUpgradeTo(targetPlan);
  }

  /**
   * Check if downgrade to target plan is possible given current user count
   */
  canDowngradeTo(targetPlan: SubscriptionPlan, currentUserCount: number): boolean {
    return this.props.plan.canDowngradeTo(targetPlan, currentUserCount);
  }

  /**
   * Update the subscription plan
   */
  updatePlan(newPlan: SubscriptionPlan, currentUserCount: number): Subscription {
    if (!this.isActive()) {
      throw new SubscriptionNotActiveException(this.props.id, this.props.status.value);
    }

    // Check if downgrade is valid
    if (!newPlan.canAccommodateUsers(currentUserCount)) {
      throw new InvalidSubscriptionDowngradeException(
        this.props.plan.value,
        newPlan.value,
        currentUserCount,
        newPlan.maxLicenses
      );
    }

    return new Subscription({
      ...this.props,
      plan: newPlan,
      updatedAt: new Date(),
    });
  }

  /**
   * Update subscription status
   */
  updateStatus(newStatus: SubscriptionStatus): Subscription {
    return new Subscription({
      ...this.props,
      status: newStatus,
      updatedAt: new Date(),
    });
  }

  /**
   * Update Stripe customer ID
   */
  updateStripeCustomerId(stripeCustomerId: string): Subscription {
    return new Subscription({
      ...this.props,
      stripeCustomerId,
      updatedAt: new Date(),
    });
  }

  /**
   * Update Stripe subscription details
   */
  updateStripeSubscription(params: {
    stripeSubscriptionId: string;
    currentPeriodStart: Date;
    currentPeriodEnd: Date;
    cancelAtPeriodEnd?: boolean;
  }): Subscription {
    return new Subscription({
      ...this.props,
      stripeSubscriptionId: params.stripeSubscriptionId,
      currentPeriodStart: params.currentPeriodStart,
      currentPeriodEnd: params.currentPeriodEnd,
      cancelAtPeriodEnd: params.cancelAtPeriodEnd ?? this.props.cancelAtPeriodEnd,
      updatedAt: new Date(),
    });
  }

  /**
   * Mark subscription as scheduled for cancellation at period end
   */
  scheduleCancellation(): Subscription {
    return new Subscription({
      ...this.props,
      cancelAtPeriodEnd: true,
      updatedAt: new Date(),
    });
  }

  /**
   * Unschedule cancellation
   */
  unscheduleCancellation(): Subscription {
    return new Subscription({
      ...this.props,
      cancelAtPeriodEnd: false,
      updatedAt: new Date(),
    });
  }

  /**
   * Cancel the subscription immediately
   */
  cancel(): Subscription {
    return new Subscription({
      ...this.props,
      status: SubscriptionStatus.canceled(),
      cancelAtPeriodEnd: false,
      updatedAt: new Date(),
    });
  }

  /**
   * Convert to plain object for persistence
   */
  toObject(): {
    id: string;
    organizationId: string;
    plan: SubscriptionPlanType;
    status: SubscriptionStatusType;
    stripeCustomerId: string | null;
    stripeSubscriptionId: string | null;
    currentPeriodStart: Date | null;
    currentPeriodEnd: Date | null;
    cancelAtPeriodEnd: boolean;
    createdAt: Date;
    updatedAt: Date;
  } {
    return {
      id: this.props.id,
      organizationId: this.props.organizationId,
      plan: this.props.plan.value,
      status: this.props.status.value,
      stripeCustomerId: this.props.stripeCustomerId,
      stripeSubscriptionId: this.props.stripeSubscriptionId,
      currentPeriodStart: this.props.currentPeriodStart,
      currentPeriodEnd: this.props.currentPeriodEnd,
      cancelAtPeriodEnd: this.props.cancelAtPeriodEnd,
      createdAt: this.props.createdAt,
      updatedAt: this.props.updatedAt,
    };
  }
}
