import Stripe from 'stripe';
import { loadBillingConfig, SubscriptionTier } from '../config/billing.config';
import { z } from 'zod';
import { prisma } from '@shieldsai/shared-db';

const config = loadBillingConfig();
const stripe = new Stripe(config.stripe.apiKey, { apiVersion: '2023-10-16' });

// Subscription service
export class SubscriptionService {
  /**
   * Create a new subscription for a customer
   */
  async createSubscription(
    customerId: string,
    tier: SubscriptionTier,
    metadata?: Record<string, string>
  ): Promise<Stripe.Subscription> {
    const priceId = config.tiers[tier].priceId;

    const subscription = await stripe.subscriptions.create({
      customer: customerId,
      items: [{ price: priceId }],
      metadata: metadata,
      proration_behavior: 'create_prorations',
    });

    return subscription;
  }

  /**
   * Update a customer's subscription tier
   */
  async updateSubscriptionTier(
    subscriptionId: string,
    newTier: SubscriptionTier
  ): Promise<Stripe.Subscription> {
    const newPriceId = config.tiers[newTier].priceId;

    const subscription = await stripe.subscriptions.update(subscriptionId, {
      items: [
        {
          price: newPriceId,
          quantity: 1,
        },
      ],
      proration_behavior: 'create_prorations',
    });

    return subscription;
  }

  /**
   * Cancel a subscription
   */
  async cancelSubscription(
    subscriptionId: string,
    atPeriodEnd: boolean = true
  ): Promise<Stripe.Subscription> {
    const subscription = await stripe.subscriptions.update(subscriptionId, {
      cancel_at_period_end: atPeriodEnd,
    });

    return subscription;
  }

  /**
   * Get subscription by ID
   */
  async getSubscription(subscriptionId: string): Promise<Stripe.Subscription | null> {
    try {
      const subscription = await stripe.subscriptions.retrieve(subscriptionId);
      return subscription;
    } catch (error) {
      if (error instanceof Stripe.errors.StripeInvalidRequestError) {
        return null;
      }
      throw error;
    }
  }

  /**
   * Get customer's current subscription
   */
  async getCustomerSubscription(customerId: string): Promise<Stripe.Subscription | null> {
    const subscriptions = await stripe.subscriptions.list({
      customer: customerId,
      status: 'active',
      limit: 1,
    });

    return subscriptions.data[0] || null;
  }
}

// Customer service
export class CustomerService {
  /**
   * Create a new Stripe customer
   */
  async createCustomer(
    email: string,
    name?: string,
    metadata?: Record<string, string>
  ): Promise<Stripe.Customer> {
    const customer = await stripe.customers.create({
      email,
      name,
      metadata,
    });

    return customer;
  }

  /**
   * Get or create customer by email
   */
  async getOrCreateCustomer(
    email: string,
    name?: string
  ): Promise<Stripe.Customer> {
    const existingCustomers = await stripe.customers.list({
      email,
      limit: 1,
    });

    if (existingCustomers.data.length > 0) {
      return existingCustomers.data[0];
    }

    return this.createCustomer(email, name);
  }

  /**
   * Create a billing portal session
   */
  async createBillingPortalSession(
    customerId: string,
    returnUrl: string
  ): Promise<Stripe.BillingPortal.Session> {
    const session = await stripe.billingPortal.sessions.create({
      customer: customerId,
      return_url: returnUrl,
    });

    return session;
  }

  /**
   * Get customer by ID
   */
  async getCustomer(customerId: string): Promise<Stripe.Customer | null> {
    try {
      const customer = await stripe.customers.retrieve(customerId);
      return customer as Stripe.Customer;
    } catch (error) {
      if (error instanceof Stripe.errors.StripeInvalidRequestError) {
        return null;
      }
      throw error;
    }
  }
}

// Webhook service
export class WebhookService {
  /**
   * Construct webhook event from raw body
   */
  constructEvent(
    rawBody: Buffer | string,
    signature: string
  ): Stripe.Event {
    return stripe.webhooks.constructEvent(
      rawBody,
      signature,
      config.stripe.webhookSecret
    );
  }

  /**
   * Handle webhook event
   */
  async handleWebhook(event: Stripe.Event): Promise<void> {
    switch (event.type) {
      case 'customer.subscription.created':
      case 'customer.subscription.updated':
        await this.handleSubscriptionChange(event.data.object);
        break;
      case 'customer.subscription.deleted':
        await this.handleSubscriptionDeleted(event.data.object);
        break;
      case 'invoice.payment_succeeded':
        await this.handlePaymentSucceeded(event.data.object);
        break;
      case 'invoice.payment_failed':
        await this.handlePaymentFailed(event.data.object);
        break;
      default:
        console.log(`Unhandled event type: ${event.type}`);
    }
  }

  private async handleSubscriptionChange(subscription: Stripe.Subscription) {
    console.log(`Subscription ${subscription.id} changed to ${subscription.status}`);
    
    const userId = subscription.metadata?.userId;
    if (!userId) {
      console.warn(`Subscription ${subscription.id} missing userId in metadata`);
      return;
    }

    const prismaStatus = this.mapStripeStatusToPrisma(subscription.status);
    
    await prisma.subscription.upsert({
      where: {
        stripeId: subscription.id,
      },
      update: {
        userId,
        tier: subscription.metadata?.tier as any || 'basic',
        status: prismaStatus,
        currentPeriodStart: new Date(subscription.current_period_start * 1000),
        currentPeriodEnd: new Date(subscription.current_period_end * 1000),
        cancelAtPeriodEnd: subscription.cancel_at_period_end,
      },
      create: {
        stripeId: subscription.id,
        userId,
        tier: subscription.metadata?.tier as any || 'basic',
        status: prismaStatus,
        currentPeriodStart: new Date(subscription.current_period_start * 1000),
        currentPeriodEnd: new Date(subscription.current_period_end * 1000),
        cancelAtPeriodEnd: subscription.cancel_at_period_end || false,
      },
    });
  }

  private async handleSubscriptionDeleted(subscription: Stripe.Subscription) {
    console.log(`Subscription ${subscription.id} deleted`);
    
    await prisma.subscription.updateMany({
      where: {
        stripeId: subscription.id,
      },
      data: {
        status: 'canceled',
      },
    });
  }

  private async handlePaymentSucceeded(invoice: Stripe.Invoice) {
    console.log(`Payment succeeded for invoice ${invoice.id}`);
    
    const subscriptionId = invoice.subscription as string;
    if (!subscriptionId) return;

    const subscription = await stripe.subscriptions.retrieve(subscriptionId);
    const userId = subscription.metadata?.userId;
    if (!userId) return;

    await prisma.subscription.updateMany({
      where: {
        stripeId: subscriptionId,
      },
      data: {
        currentPeriodStart: new Date(invoice.period_start * 1000),
        currentPeriodEnd: new Date(invoice.period_end * 1000),
      },
    });
  }

  private async handlePaymentFailed(invoice: Stripe.Invoice) {
    console.log(`Payment failed for invoice ${invoice.id}`);
    
    const subscriptionId = invoice.subscription as string;
    if (!subscriptionId) return;

    await prisma.subscription.updateMany({
      where: {
        stripeId: subscriptionId,
      },
      data: {
        status: 'past_due',
      },
    });
  }

  private mapStripeStatusToPrisma(status: string): 'active' | 'past_due' | 'canceled' | 'unpaid' | 'trialing' {
    switch (status) {
      case 'active':
        return 'active';
      case 'canceled':
        return 'canceled';
      case 'past_due':
        return 'past_due';
      case 'unpaid':
        return 'unpaid';
      case 'trialing':
        return 'trialing';
      default:
        return 'past_due';
    }
  }
}

// Export instances
export const subscriptionService = new SubscriptionService();
export const customerService = new CustomerService();
export const webhookService = new WebhookService();
