/**
 * Account Management Routes
 *
 * User account, API keys, usage, and subscription management
 */

import { Router, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { authenticateJWT, authenticateApiKey } from '../middleware/auth';
import { validate } from '../middleware/validator';
import { z } from 'zod';
import { createApiKey, getUserApiKeys, revokeApiKey } from '../auth/apiKeyService';
import { getCurrentBalance } from '../services/creditService';
import { logger } from '../utils/logger';
import { tierService } from '../services/tierService';
import { createSubscription, cancelSubscription } from '../services/paypalService';
import { createLicense, activateLicense } from '../services/multilingualLicenseService';

const router = Router();
const prisma = new PrismaClient();

// Validation schemas
const createApiKeySchema = z.object({
  name: z.string().min(1).max(100),
});

const checkoutSchema = z.object({
  planTier: z.string().min(1).max(50),
  billingCycle: z.enum(['monthly', 'annual']),
  plugin: z.enum(['translate', 'multilingual', 'international']),
  site_url: z.string().url('site_url must be a valid URL'),
});

const CHECKOUT_SESSION_TTL_MS = 24 * 60 * 60 * 1000; // 24h

const updateProfileSchema = z.object({
  email: z.string().email().optional(),
});

/**
 * GET /v1/account/validate
 * Validate API key (no JWT required)
 */
router.get('/validate', authenticateApiKey, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    return res.json({
      valid: true,
      user: {
        id: req.user.userId,
        email: req.user.email,
      },
    });
  } catch (error) {
    logger.error('Validate API key error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to validate API key' },
    });
  }
});

/**
 * GET /v1/account/profile
 * Get user profile
 */
router.get('/profile', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const user = await prisma.user.findUnique({
      where: { id: req.user.userId },
      select: {
        id: true,
        email: true,
        email_verified: true,
        status: true,
        created_at: true,
        updated_at: true,
      },
    });

    if (!user) {
      return res.status(404).json({
        error: { code: 'USER_NOT_FOUND', message: 'User not found' },
      });
    }

    return res.json({
      id: user.id,
      email: user.email,
      emailVerified: user.email_verified,
      status: user.status,
      createdAt: user.created_at.toISOString(),
      updatedAt: user.updated_at.toISOString(),
    });
  } catch (error) {
    logger.error('Get profile error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to get user profile' },
    });
  }
});

/**
 * PATCH /v1/account/profile
 * Update user profile
 */
router.patch('/profile', authenticateJWT, validate(updateProfileSchema), async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const { email } = req.body;

    // Only update if email is provided
    if (!email) {
      return res.status(400).json({
        error: { code: 'INVALID_REQUEST', message: 'No updates provided' },
      });
    }

    // Check if email is already in use by another user
    const existingUser = await prisma.user.findUnique({
      where: { email },
    });

    if (existingUser && existingUser.id !== req.user.userId) {
      return res.status(409).json({
        error: { code: 'EMAIL_IN_USE', message: 'Email address is already in use' },
      });
    }

    // Update user email
    const updatedUser = await prisma.user.update({
      where: { id: req.user.userId },
      data: {
        email,
        email_verified: false, // Reset verification status when email changes
      },
      select: {
        id: true,
        email: true,
        email_verified: true,
        status: true,
        created_at: true,
        updated_at: true,
      },
    });

    logger.info('User profile updated', {
      userId: req.user.userId,
      newEmail: email,
    });

    return res.json({
      id: updatedUser.id,
      email: updatedUser.email,
      emailVerified: updatedUser.email_verified,
      status: updatedUser.status,
      createdAt: updatedUser.created_at.toISOString(),
      updatedAt: updatedUser.updated_at.toISOString(),
    });
  } catch (error) {
    logger.error('Update profile error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to update user profile' },
    });
  }
});

/**
 * GET /v1/account/transactions
 * Get credit transaction history
 */
router.get('/transactions', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
    const offset = Math.max(parseInt(req.query.offset as string) || 0, 0);

    const [transactions, total] = await Promise.all([
      prisma.creditTransaction.findMany({
        where: { user_id: req.user.userId },
        orderBy: { created_at: 'desc' },
        take: limit,
        skip: offset,
        select: {
          id: true,
          type: true,
          amount: true,
          balance_after: true,
          description: true,
          related_job_id: true,
          related_payment_id: true,
          created_at: true,
        },
      }),
      prisma.creditTransaction.count({
        where: { user_id: req.user.userId },
      }),
    ]);

    return res.json({
      transactions: transactions.map(tx => ({
        id: tx.id,
        type: tx.type,
        amount: tx.amount,
        balanceAfter: tx.balance_after,
        description: tx.description,
        relatedJobId: tx.related_job_id,
        relatedPaymentId: tx.related_payment_id,
        createdAt: tx.created_at.toISOString(),
      })),
      pagination: {
        limit,
        offset,
        total,
        hasMore: offset + limit < total,
      },
    });
  } catch (error) {
    logger.error('Get transactions error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to get transaction history' },
    });
  }
});

/**
 * GET /v1/account/credits
 * Get current credit balance and subscription info (no JWT required, uses API key)
 */
router.get('/credits', authenticateApiKey, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const user = await prisma.user.findUnique({
      where: { id: req.user.userId },
      include: {
        subscriptions: true,
      },
    });

    if (!user) {
      return res.status(404).json({
        error: { code: 'USER_NOT_FOUND', message: 'User not found' },
      });
    }

    const creditBalance = await getCurrentBalance(user.id);

    // Pick first active subscription (translate plugin preferred for API key auth)
    const sub = user.subscriptions.find(s => s.status === 'active') || user.subscriptions[0];

    return res.json({
      credits_balance: creditBalance,
      subscription: sub ? {
        tier: sub.plan_tier,
        status: sub.status,
        current_period_end: sub.current_period_end,
      } : null,
    });
  } catch (error) {
    logger.error('Get credits error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to get credit balance' },
    });
  }
});

/**
 * GET /v1/account
 * Get account details
 */
router.get('/', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const user = await prisma.user.findUnique({
      where: { id: req.user.userId },
      include: {
        subscriptions: true,
      },
    });

    if (!user) {
      return res.status(404).json({
        error: { code: 'USER_NOT_FOUND', message: 'User not found' },
      });
    }

    const creditBalance = await getCurrentBalance(user.id);

    return res.json({
      user: {
        id: user.id,
        email: user.email,
        emailVerified: user.email_verified,
        status: user.status,
        createdAt: user.created_at,
      },
      subscriptions: user.subscriptions.map(s => ({
        plugin: s.plugin,
        planTier: s.plan_tier,
        billingCycle: s.billing_cycle,
        status: s.status,
        currentPeriodStart: s.current_period_start,
        currentPeriodEnd: s.current_period_end,
        cancelAtPeriodEnd: s.cancel_at_period_end,
      })),
      credits: {
        balance: creditBalance,
        allocation: user.subscriptions.length > 0
          ? await tierService.getCreditAllocation(
              user.subscriptions[0].plan_tier,
              user.subscriptions[0].plugin
            )
          : 0,
      },
    });
  } catch (error) {
    logger.error('Get account error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to get account details' },
    });
  }
});

/**
 * POST /v1/account/api-keys
 * Generate new API key
 */
router.post('/api-keys', authenticateJWT, validate(createApiKeySchema), async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const { name } = req.body;

    const apiKey = await createApiKey(req.user.userId, name);

    return res.status(201).json(apiKey);
  } catch (error) {
    logger.error('Create API key error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to create API key' },
    });
  }
});

/**
 * GET /v1/account/api-keys
 * List user's API keys
 */
router.get('/api-keys', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const apiKeys = await getUserApiKeys(req.user.userId);

    return res.json({
      apiKeys: apiKeys.map(key => ({
        id: key.id,
        name: key.name,
        prefix: key.prefix,
        isActive: key.isActive,
        lastUsedAt: key.lastUsedAt,
        createdAt: key.createdAt,
      })),
    });
  } catch (error) {
    logger.error('List API keys error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to list API keys' },
    });
  }
});

/**
 * DELETE /v1/account/api-keys/:keyId
 * Revoke API key
 */
router.delete('/api-keys/:keyId', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const { keyId } = req.params;

    const revoked = await revokeApiKey(keyId, req.user.userId);

    if (!revoked) {
      return res.status(404).json({
        error: { code: 'NOT_FOUND', message: 'API key not found' },
      });
    }

    return res.json({
      message: 'API key revoked successfully',
    });
  } catch (error) {
    logger.error('Revoke API key error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to revoke API key' },
    });
  }
});

/**
 * GET /v1/account/jobs
 * Get translation jobs (API key authenticated for WordPress plugin)
 */
router.get('/jobs', authenticateApiKey, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const status = req.query.status as string | undefined;
    const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);

    // Build query filter
    const where: any = { user_id: req.user.userId };
    if (status && ['pending', 'processing', 'completed', 'failed'].includes(status)) {
      where.status = status;
    }

    const jobs = await prisma.translationJob.findMany({
      where,
      orderBy: { created_at: 'desc' },
      take: limit,
      select: {
        id: true,
        status: true,
        source_lang: true,
        target_lang: true,
        model: true,
        characters_used: true,
        tokens_used: true,
        cost: true,
        error_message: true,
        created_at: true,
        completed_at: true,
      },
    });

    return res.json({
      jobs: jobs.map(job => ({
        id: job.id,
        status: job.status,
        source_lang: job.source_lang,
        target_lang: job.target_lang,
        model: job.model,
        characters_used: job.characters_used,
        cost: job.cost.toString(),
        error_message: job.error_message,
        created_at: job.created_at.toISOString(),
        completed_at: job.completed_at?.toISOString() || null,
      })),
    });
  } catch (error) {
    logger.error('Get jobs error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to get translation jobs' },
    });
  }
});

/**
 * GET /v1/account/usage
 * Get usage history
 */
router.get('/usage', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const page = parseInt(req.query.page as string) || 1;
    const limit = parseInt(req.query.limit as string) || 50;
    const skip = (page - 1) * limit;

    const [transactions, jobs, total] = await Promise.all([
      prisma.creditTransaction.findMany({
        where: { user_id: req.user.userId },
        orderBy: { created_at: 'desc' },
        take: limit,
        skip,
      }),
      prisma.translationJob.findMany({
        where: { user_id: req.user.userId },
        orderBy: { created_at: 'desc' },
        take: limit,
        skip,
      }),
      prisma.creditTransaction.count({
        where: { user_id: req.user.userId },
      }),
    ]);

    return res.json({
      transactions,
      jobs,
      pagination: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit),
      },
    });
  } catch (error) {
    logger.error('Get usage error', { error });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to get usage history' },
    });
  }
});

/**
 * GET /v1/subscriptions/plans
 * List available subscription plans (public)
 */
router.get('/subscriptions/plans', async (req: Request, res: Response) => {
  try {
    const plugin = req.query.plugin as string | undefined;
    const activeTiers = plugin
      ? await tierService.getActiveTiersByPlugin(plugin)
      : await tierService.getActiveTiers();

    const plans = activeTiers.map((tier) => ({
      tier: tier.slug,
      name: tier.name,
      description: tier.description,
      monthlyPrice: parseFloat(tier.monthly_price.toString()),
      annualPrice: parseFloat(tier.annual_price.toString()),
      credits: tier.credit_allocation,
      models: tier.allowed_models as string[],
      features: tier.features as string[],
      rateLimit: tier.rate_limit,
    }));

    res.json({ plans });
  } catch (error) {
    logger.error('Failed to fetch subscription plans', { error });
    res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to fetch subscription plans' },
    });
  }
});

/**
 * POST /v1/subscriptions/checkout
 * Create PayPal checkout session
 */
router.post('/subscriptions/checkout', authenticateJWT, validate(checkoutSchema), async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const { planTier, billingCycle, plugin, site_url } = req.body;

    const tier = await tierService.getTierBySlug(planTier, plugin);
    if (!tier || !tier.is_active) {
      return res.status(400).json({
        error: { code: 'INVALID_PLAN_TIER', message: `Invalid plan tier: '${planTier}' for plugin '${plugin}'` },
      });
    }

    const existingSubscription = await prisma.subscription.findFirst({
      where: {
        user_id: req.user.userId,
        plugin,
        status: { in: ['active', 'suspended'] },
      },
    });

    if (existingSubscription) {
      return res.status(409).json({
        error: {
          code: 'SUBSCRIPTION_EXISTS',
          message: 'An active subscription already exists. Cancel it before creating a new one.',
        },
      });
    }

    // Record intent server-side before redirect. site_url and plugin never travel
    // through the PayPal webhook payload as trusted data — the webhook looks them
    // up from this row using the session id carried in `custom_id`.
    const session = await prisma.checkoutSession.create({
      data: {
        user_id: req.user.userId,
        plugin,
        site_url,
        plan_tier: planTier,
        billing_cycle: billingCycle,
        status: 'pending',
        expires_at: new Date(Date.now() + CHECKOUT_SESSION_TTL_MS),
      },
    });

    const { approvalUrl, subscriptionId } = await createSubscription(
      req.user.userId,
      planTier as any,
      billingCycle as any,
      session.id,
      plugin
    );

    await prisma.checkoutSession.update({
      where: { id: session.id },
      data: { paypal_subscription_id: subscriptionId },
    });

    logger.info('Checkout session created', {
      userId: req.user.userId,
      sessionId: session.id,
      plugin,
      planTier,
      billingCycle,
      subscriptionId,
    });

    return res.json({
      approval_url: approvalUrl,
      subscription_id: subscriptionId,
      checkout_session_id: session.id,
    });
  } catch (error) {
    logger.error('Create checkout error', {
      userId: req.user?.userId,
      error: error instanceof Error ? error.message : 'Unknown error',
      stack: error instanceof Error ? error.stack : undefined,
    });

    if (error instanceof Error) {
      if (error.message.includes('Invalid plan tier')) {
        return res.status(400).json({
          error: { code: 'INVALID_PLAN', message: error.message },
        });
      }
      if (error.message.includes('User not found')) {
        return res.status(404).json({
          error: { code: 'USER_NOT_FOUND', message: 'User not found' },
        });
      }
      if (error.message.includes('PayPal configuration')) {
        return res.status(503).json({
          error: { code: 'SERVICE_UNAVAILABLE', message: 'Payment service is not configured' },
        });
      }
    }

    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to create checkout session' },
    });
  }
});

/**
 * GET /v1/subscriptions/checkout-sessions/:id
 *
 * Plugin polls this endpoint after the user returns from PayPal. First successful
 * retrieval for a `paid` session mints a License + ApiKey on the fly, activates
 * the license on the session's recorded site_url, and transitions the session to
 * `retrieved`. Subsequent polls get `already_retrieved` without the plaintext.
 *
 * Single-use by construction: the prisma.update with `where: { status: 'paid' }`
 * fails (throws P2025) if a racing poll got there first.
 */
router.get('/subscriptions/checkout-sessions/:id', authenticateJWT, async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    const session = await prisma.checkoutSession.findUnique({
      where: { id: req.params.id },
    });

    if (!session || session.user_id !== req.user.userId) {
      return res.status(404).json({
        error: { code: 'NOT_FOUND', message: 'Checkout session not found' },
      });
    }

    if (session.status === 'retrieved') {
      return res.json({ status: 'already_retrieved' });
    }

    if (session.status === 'expired' || session.expires_at < new Date()) {
      if (session.status !== 'expired') {
        await prisma.checkoutSession.update({
          where: { id: session.id },
          data: { status: 'expired' },
        });
      }
      return res.status(410).json({
        error: { code: 'SESSION_EXPIRED', message: 'Checkout session has expired' },
      });
    }

    if (session.status === 'failed') {
      return res.status(410).json({
        error: { code: 'SESSION_FAILED', message: 'Checkout session failed to complete' },
      });
    }

    if (session.status === 'pending') {
      return res.json({ status: 'pending' });
    }

    // status === 'paid' → claim the session race-safely, THEN mint. If a
    // concurrent poll already flipped it to 'retrieved', count === 0 and we
    // return already_retrieved without minting orphan credentials.
    const claim = await prisma.checkoutSession.updateMany({
      where: { id: session.id, status: 'paid' },
      data: { status: 'retrieved', retrieved_at: new Date() },
    });

    if (claim.count === 0) {
      return res.json({ status: 'already_retrieved' });
    }

    const subscription = await prisma.subscription.findFirst({
      where: { user_id: req.user.userId, plugin: session.plugin },
    });

    if (!subscription) {
      // Roll the session back to 'paid' so the user can retry — this is the
      // race window between webhook ACTIVATED firing and the Subscription row
      // becoming visible. Rare but recoverable.
      await prisma.checkoutSession.updateMany({
        where: { id: session.id, status: 'retrieved' },
        data: { status: 'paid', retrieved_at: null },
      });
      logger.warn('Paid session has no subscription row yet', { sessionId: session.id });
      return res.status(503).json({
        error: { code: 'NOT_READY', message: 'Subscription not yet provisioned, retry shortly' },
      });
    }

    const sitesAllowed = (() => {
      switch (session.plan_tier) {
        case 'starter': return 3;
        case 'professional': return 10;
        case 'enterprise': return -1;
        default: return 3;
      }
    })();

    let licenseResult: Awaited<ReturnType<typeof createLicense>>;
    let licenseInfo: Awaited<ReturnType<typeof activateLicense>>;
    try {
      licenseResult = await createLicense({
        plan_tier: session.plan_tier,
        sites_allowed: sitesAllowed,
        expires_at: subscription.current_period_end,
        plugin: session.plugin,
        user_id: req.user.userId,
      });
      licenseInfo = await activateLicense({
        license_key: licenseResult.license_key,
        site_url: session.site_url,
        plugin: session.plugin,
      });
    } catch (mintError) {
      // Session is already claimed ('retrieved'), but we failed to mint.
      // Mark it failed so the user gets a clear error on next poll; support
      // can manually re-issue keys if the payment is real.
      await prisma.checkoutSession.updateMany({
        where: { id: session.id },
        data: { status: 'failed' },
      });
      throw mintError;
    }

    logger.info('Checkout session retrieved', {
      userId: req.user.userId,
      sessionId: session.id,
      plugin: session.plugin,
    });

    return res.json({
      status: 'retrieved',
      license_key: licenseResult.license_key,
      subscription: {
        plugin: subscription.plugin,
        tier: subscription.plan_tier,
        status: subscription.status,
        current_period_end: subscription.current_period_end.toISOString(),
      },
      license: {
        tier: licenseInfo.tier,
        status: licenseInfo.status,
        sites_allowed: licenseInfo.sites_allowed,
        languages_allowed: licenseInfo.languages_allowed,
        expires_at: licenseInfo.expires_at,
      },
    });
  } catch (error) {
    logger.error('Retrieve checkout session error', {
      userId: req.user?.userId,
      sessionId: req.params.id,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to retrieve checkout session' },
    });
  }
});

/**
 * POST /v1/subscriptions/cancel
 * Cancel user's active subscription
 */
const cancelSchema = z.object({
  reason: z.string().max(500).optional(),
});

router.post('/subscriptions/cancel', authenticateJWT, validate(cancelSchema), async (req: Request, res: Response) => {
  try {
    if (!req.user) {
      return res.status(401).json({
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' },
      });
    }

    // Find user's active subscription
    const subscription = await prisma.subscription.findFirst({
      where: {
        user_id: req.user.userId,
        plugin: 'translate',
        status: { in: ['active', 'suspended'] },
      },
    });

    if (!subscription || !subscription.paypal_subscription_id) {
      return res.status(404).json({
        error: { code: 'NO_ACTIVE_SUBSCRIPTION', message: 'No active subscription found' },
      });
    }

    await cancelSubscription(
      req.user.userId,
      subscription.paypal_subscription_id,
      req.body.reason
    );

    logger.info('Subscription cancelled via API', {
      userId: req.user.userId,
      subscriptionId: subscription.paypal_subscription_id,
    });

    return res.json({ success: true, message: 'Subscription cancelled successfully' });
  } catch (error) {
    logger.error('Cancel subscription error', {
      userId: req.user?.userId,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
    return res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Failed to cancel subscription' },
    });
  }
});

export default router;
