/**
 * Stripe Adapter
 *
 * Implementation of the StripePort interface using the Stripe SDK.
 */

import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Stripe from 'stripe';
import {
  StripePort,
  CreateCheckoutSessionInput,
  CreateCheckoutSessionOutput,
  CreateCommissionCheckoutInput,
  CreateCommissionCheckoutOutput,
  CreatePortalSessionInput,
  CreatePortalSessionOutput,
  StripeSubscriptionData,
  StripeCheckoutSessionData,
  StripeWebhookEvent,
} from '@domain/ports/out/stripe.port';
import { SubscriptionPlanType } from '@domain/value-objects/subscription-plan.vo';

@Injectable()
export class StripeAdapter implements StripePort {
  private readonly logger = new Logger(StripeAdapter.name);
  private readonly stripe: Stripe;
  private readonly webhookSecret: string;
  private readonly priceIdMap: Map<string, SubscriptionPlanType>;
  private readonly planPriceMap: Map<string, { monthly: string; yearly: string }>;

  constructor(private readonly configService: ConfigService) {
    const apiKey = this.configService.get<string>('STRIPE_SECRET_KEY');
    if (!apiKey) {
      this.logger.warn('STRIPE_SECRET_KEY not configured - Stripe features will be disabled');
    }

    this.stripe = new Stripe(apiKey || 'sk_test_placeholder');

    this.webhookSecret = this.configService.get<string>('STRIPE_WEBHOOK_SECRET') || '';

    // Map Stripe price IDs to plans
    this.priceIdMap = new Map();
    this.planPriceMap = new Map();

    // Configure plan price IDs from environment
    const silverMonthly = this.configService.get<string>('STRIPE_SILVER_MONTHLY_PRICE_ID');
    const silverYearly = this.configService.get<string>('STRIPE_SILVER_YEARLY_PRICE_ID');
    const goldMonthly = this.configService.get<string>('STRIPE_GOLD_MONTHLY_PRICE_ID');
    const goldYearly = this.configService.get<string>('STRIPE_GOLD_YEARLY_PRICE_ID');
    const platiniumMonthly = this.configService.get<string>('STRIPE_PLATINIUM_MONTHLY_PRICE_ID');
    const platiniumYearly = this.configService.get<string>('STRIPE_PLATINIUM_YEARLY_PRICE_ID');

    if (silverMonthly) this.priceIdMap.set(silverMonthly, 'SILVER');
    if (silverYearly) this.priceIdMap.set(silverYearly, 'SILVER');
    if (goldMonthly) this.priceIdMap.set(goldMonthly, 'GOLD');
    if (goldYearly) this.priceIdMap.set(goldYearly, 'GOLD');
    if (platiniumMonthly) this.priceIdMap.set(platiniumMonthly, 'PLATINIUM');
    if (platiniumYearly) this.priceIdMap.set(platiniumYearly, 'PLATINIUM');

    this.planPriceMap.set('SILVER', {
      monthly: silverMonthly || '',
      yearly: silverYearly || '',
    });
    this.planPriceMap.set('GOLD', {
      monthly: goldMonthly || '',
      yearly: goldYearly || '',
    });
    this.planPriceMap.set('PLATINIUM', {
      monthly: platiniumMonthly || '',
      yearly: platiniumYearly || '',
    });
  }

  async createCheckoutSession(
    input: CreateCheckoutSessionInput
  ): Promise<CreateCheckoutSessionOutput> {
    const planPrices = this.planPriceMap.get(input.plan);
    if (!planPrices) {
      throw new Error(`No price configuration for plan: ${input.plan}`);
    }

    const priceId = input.billingInterval === 'yearly' ? planPrices.yearly : planPrices.monthly;

    if (!priceId) {
      throw new Error(`No ${input.billingInterval} price configured for plan: ${input.plan}`);
    }

    const sessionParams: Stripe.Checkout.SessionCreateParams = {
      mode: 'subscription',
      payment_method_types: ['card'],
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      success_url: input.successUrl,
      cancel_url: input.cancelUrl,
      customer_email: input.customerId ? undefined : input.email,
      customer: input.customerId || undefined,
      metadata: {
        organizationId: input.organizationId,
        organizationName: input.organizationName,
        plan: input.plan,
      },
      subscription_data: {
        metadata: {
          organizationId: input.organizationId,
          plan: input.plan,
        },
      },
      allow_promotion_codes: true,
      billing_address_collection: 'required',
    };

    const session = await this.stripe.checkout.sessions.create(sessionParams);

    this.logger.log(
      `Created checkout session ${session.id} for organization ${input.organizationId}`
    );

    return {
      sessionId: session.id,
      sessionUrl: session.url || '',
    };
  }

  async createCommissionCheckout(
    input: CreateCommissionCheckoutInput
  ): Promise<CreateCommissionCheckoutOutput> {
    const session = await this.stripe.checkout.sessions.create({
      mode: 'payment',
      payment_method_types: ['card'],
      line_items: [
        {
          price_data: {
            currency: input.currency,
            unit_amount: input.amountCents,
            product_data: {
              name: 'Commission Xpeditis',
              description: input.bookingDescription,
            },
          },
          quantity: 1,
        },
      ],
      customer_email: input.customerEmail,
      success_url: input.successUrl,
      cancel_url: input.cancelUrl,
      metadata: {
        type: 'commission',
        bookingId: input.bookingId,
        organizationId: input.organizationId,
      },
    });

    this.logger.log(
      `Created commission checkout session ${session.id} for booking ${input.bookingId}`
    );

    return {
      sessionId: session.id,
      sessionUrl: session.url || '',
    };
  }

  async createPortalSession(input: CreatePortalSessionInput): Promise<CreatePortalSessionOutput> {
    const session = await this.stripe.billingPortal.sessions.create({
      customer: input.customerId,
      return_url: input.returnUrl,
    });

    this.logger.log(`Created portal session for customer ${input.customerId}`);

    return {
      sessionUrl: session.url,
    };
  }

  async getSubscription(subscriptionId: string): Promise<StripeSubscriptionData | null> {
    try {
      const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);

      // Get the price ID from the first item
      const priceId = subscription.items.data[0]?.price.id || '';

      return {
        subscriptionId: subscription.id,
        customerId: subscription.customer as string,
        status: subscription.status,
        planId: priceId,
        currentPeriodStart: new Date(subscription.current_period_start * 1000),
        currentPeriodEnd: new Date(subscription.current_period_end * 1000),
        cancelAtPeriodEnd: subscription.cancel_at_period_end,
      };
    } catch (error) {
      if ((error as any).code === 'resource_missing') {
        return null;
      }
      throw error;
    }
  }

  async getCheckoutSession(sessionId: string): Promise<StripeCheckoutSessionData | null> {
    try {
      const session = await this.stripe.checkout.sessions.retrieve(sessionId);

      return {
        sessionId: session.id,
        customerId: session.customer as string | null,
        subscriptionId: session.subscription as string | null,
        status: session.status || 'unknown',
        metadata: (session.metadata || {}) as Record<string, string>,
      };
    } catch (error) {
      if ((error as any).code === 'resource_missing') {
        return null;
      }
      this.logger.error(`Failed to retrieve checkout session ${sessionId}:`, error);
      throw error;
    }
  }

  async cancelSubscriptionAtPeriodEnd(subscriptionId: string): Promise<void> {
    await this.stripe.subscriptions.update(subscriptionId, {
      cancel_at_period_end: true,
    });

    this.logger.log(`Scheduled subscription ${subscriptionId} for cancellation at period end`);
  }

  async cancelSubscriptionImmediately(subscriptionId: string): Promise<void> {
    await this.stripe.subscriptions.cancel(subscriptionId);

    this.logger.log(`Cancelled subscription ${subscriptionId} immediately`);
  }

  async resumeSubscription(subscriptionId: string): Promise<void> {
    await this.stripe.subscriptions.update(subscriptionId, {
      cancel_at_period_end: false,
    });

    this.logger.log(`Resumed subscription ${subscriptionId}`);
  }

  async constructWebhookEvent(
    payload: string | Buffer,
    signature: string
  ): Promise<StripeWebhookEvent> {
    const event = this.stripe.webhooks.constructEvent(payload, signature, this.webhookSecret);

    return {
      type: event.type,
      data: {
        object: event.data.object as Record<string, unknown>,
      },
    };
  }

  mapPriceIdToPlan(priceId: string): SubscriptionPlanType | null {
    return this.priceIdMap.get(priceId) || null;
  }
}
