/**
 * Authentication Middleware
 *
 * Provides API key and JWT authentication for protected routes
 */

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { PrismaClient } from '@prisma/client';
import { config } from '../config';
import { JWTPayload, AdminRole } from '../types';
import { logger } from '../utils/logger';
import { errorResponse } from '../utils/errorHandler';

const prisma = new PrismaClient();

/**
 * Hash API key using SHA-256
 */
function hashApiKey(key: string): string {
  return crypto.createHash('sha256').update(key).digest('hex');
}

function normalizeSiteOrigin(value: string): string | null {
  try {
    const url = new URL(value);
    if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
    return url.origin;
  } catch {
    return null;
  }
}

function isLicenseKey(value: string): boolean {
  return /^(?:INTL|MULT|TRAN)-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(value);
}

async function authenticateLicense(
  licenseKey: string,
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> {
  const requestedPlugin = typeof req.headers['x-plugin'] === 'string'
    ? req.headers['x-plugin'].trim()
    : '';
  if (!requestedPlugin) {
    res.status(401).json(errorResponse('PLUGIN_REQUIRED', 'X-Plugin header is required for license authentication'));
    return;
  }

  const rawSiteUrl = typeof req.headers['x-site-url'] === 'string'
    ? req.headers['x-site-url'].trim()
    : '';
  const siteOrigin = normalizeSiteOrigin(rawSiteUrl);
  if (!siteOrigin) {
    res.status(401).json(errorResponse('SITE_URL_REQUIRED', 'A valid X-Site-URL header is required for license authentication'));
    return;
  }

  const license = await prisma.license.findUnique({
    where: { key_hash: hashApiKey(licenseKey) },
    include: {
      activations: {
        where: { deactivated_at: null },
        select: { site_url: true },
      },
      user: {
        include: { subscriptions: true },
      },
    },
  });

  if (!license) {
    res.status(401).json(errorResponse('INVALID_LICENSE', 'Invalid license'));
    return;
  }
  if (license.status !== 'active') {
    res.status(403).json(errorResponse('LICENSE_NOT_ACTIVE', 'License is not active'));
    return;
  }
  if (license.expires_at.getTime() < Date.now()) {
    res.status(403).json(errorResponse('LICENSE_EXPIRED', 'License has expired'));
    return;
  }
  if (license.plugin !== requestedPlugin) {
    res.status(403).json(errorResponse('LICENSE_PLUGIN_MISMATCH', 'License is not valid for this plugin'));
    return;
  }
  if (!license.activations.some(activation => normalizeSiteOrigin(activation.site_url) === siteOrigin)) {
    res.status(403).json(errorResponse('LICENSE_SITE_MISMATCH', 'License is not activated for this site'));
    return;
  }
  if (!license.user_id || !license.user) {
    res.status(403).json(errorResponse('LICENSE_OWNER_REQUIRED', 'License is not linked to an account'));
    return;
  }
  if (license.user.status !== 'active') {
    res.status(403).json(errorResponse('ACCOUNT_SUSPENDED', 'Your account has been suspended'));
    return;
  }

  const activeSubscription = license.user.subscriptions.find(
    subscription => subscription.plugin === requestedPlugin && subscription.status === 'active'
  );
  if (!activeSubscription) {
    res.status(403).json(errorResponse('SUBSCRIPTION_REQUIRED', 'Active plugin subscription required'));
    return;
  }

  req.siteUrl = siteOrigin;
  req.license = {
    id: license.id,
    userId: license.user_id,
    plugin: license.plugin,
    planTier: license.plan_tier,
    executionMode: license.execution_mode,
    expiresAt: license.expires_at,
  };
  req.user = {
    userId: license.user.id,
    email: license.user.email,
    plan: activeSubscription.plan_tier as any,
    subscriptionStatus: activeSubscription.status as any,
    plugin: activeSubscription.plugin,
  };

  next();
}

/**
 * Authenticate using API key from Authorization header
 *
 * Expects: Authorization: Bearer sk_live_... or Authorization: Bearer sk_test_...
 */
export async function authenticateApiKey(
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> {
  try {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json(errorResponse('MISSING_API_KEY', 'API key is required. Use Authorization: Bearer <api_key>'));
      return;
    }

    const apiKey = authHeader.substring(7);

    if (isLicenseKey(apiKey)) {
      await authenticateLicense(apiKey, req, res, next);
      return;
    }

    if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
      res.status(401).json(errorResponse('INVALID_API_KEY_FORMAT', 'API key must start with sk_live_ or sk_test_'));
      return;
    }

    // Hash the API key
    const keyHash = hashApiKey(apiKey);

    // Look up API key in database
    const apiKeyData = await prisma.apiKey.findUnique({
      where: { key_hash: keyHash },
      include: {
        user: {
          include: {
            subscriptions: true,
          },
        },
      },
    });

    if (!apiKeyData) {
      res.status(401).json(errorResponse('INVALID_API_KEY', 'Invalid API key'));
      return;
    }

    // Check if API key is active
    if (!apiKeyData.is_active) {
      res.status(401).json(errorResponse('API_KEY_INACTIVE', 'This API key has been deactivated'));
      return;
    }

    // Check if user account is active
    if (apiKeyData.user.status !== 'active') {
      res.status(403).json(errorResponse('ACCOUNT_SUSPENDED', 'Your account has been suspended'));
      return;
    }

    const requestedPlugin = req.headers['x-plugin'] as string | undefined;
    if (requestedPlugin === 'international') {
      res.status(401).json(errorResponse('LICENSE_REQUIRED', 'International Press Zone requires license authentication'));
      return;
    }
    const activeSub = requestedPlugin
      ? apiKeyData.user.subscriptions.find(s => s.status === 'active' && s.plugin === requestedPlugin)
      : apiKeyData.user.subscriptions.find(s => s.status === 'active');
    if (!activeSub) {
      const code = requestedPlugin ? 'PLUGIN_SCOPE_MISMATCH' : 'SUBSCRIPTION_REQUIRED';
      res.status(403).json(errorResponse(code, 'Active subscription required to use the API'));
      return;
    }

    // Update last used timestamp (non-blocking)
    prisma.apiKey
      .update({
        where: { id: apiKeyData.id },
        data: { last_used_at: new Date() },
      })
      .catch((error) => {
        logger.error('Failed to update API key last used timestamp', { error, apiKeyId: apiKeyData.id });
      });

    // Attach API key data and user to request
    req.apiKey = {
      id: apiKeyData.id,
      userId: apiKeyData.user_id,
      keyHash: apiKeyData.key_hash,
      prefix: apiKeyData.prefix,
      name: apiKeyData.name,
      isActive: apiKeyData.is_active,
      lastUsedAt: apiKeyData.last_used_at,
      createdAt: apiKeyData.created_at,
    };

    // Also attach user data for convenience (similar to JWT payload)
    req.user = {
      userId: apiKeyData.user.id,
      email: apiKeyData.user.email,
      plan: activeSub.plan_tier as any,
      subscriptionStatus: activeSub.status as any,
      plugin: activeSub.plugin,
    };

    next();
  } catch (error) {
    logger.error('Error in authenticateApiKey middleware', { error });
    res.status(500).json(errorResponse('AUTHENTICATION_ERROR', 'Internal authentication error'));
  }
}

/**
 * Authenticate using JWT token from Authorization header
 *
 * Expects: Authorization: Bearer <jwt_token>
 */
export async function authenticateJWT(
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> {
  try {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json({
        error: true,
        code: 'MISSING_TOKEN',
        message: 'JWT token is required. Use Authorization: Bearer <token>',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const token = authHeader.substring(7); // Remove "Bearer " prefix

    // Verify JWT token
    let payload: JWTPayload;
    try {
      payload = jwt.verify(token, config.jwtAccessSecret) as JWTPayload;
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        res.status(401).json({
          error: true,
          code: 'TOKEN_EXPIRED',
          message: 'JWT token has expired',
          timestamp: new Date().toISOString(),
        });
        return;
      } else if (error instanceof jwt.JsonWebTokenError) {
        res.status(401).json({
          error: true,
          code: 'INVALID_TOKEN',
          message: 'Invalid JWT token',
          timestamp: new Date().toISOString(),
        });
        return;
      }
      throw error;
    }

    // Verify user exists and is active
    const user = await prisma.user.findUnique({
      where: { id: payload.userId },
      include: { subscriptions: true },
    });

    if (!user) {
      res.status(401).json({
        error: true,
        code: 'USER_NOT_FOUND',
        message: 'User not found',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    if (user.status !== 'active') {
      res.status(403).json({
        error: true,
        code: 'ACCOUNT_SUSPENDED',
        message: 'Your account has been suspended',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    // Pick first active subscription for JWT claims
    const activeSub = user.subscriptions.find(s => s.status === 'active') || user.subscriptions[0];

    // Attach user data to request
    req.user = {
      userId: user.id,
      email: user.email,
      plan: (activeSub?.plan_tier as any) || 'starter',
      subscriptionStatus: (activeSub?.status as any) || 'active',
      plugin: activeSub?.plugin || 'translate',
    };

    next();
  } catch (error) {
    logger.error('Error in authenticateJWT middleware', { error });
    res.status(500).json({
      error: true,
      code: 'AUTHENTICATION_ERROR',
      message: 'Internal authentication error',
      timestamp: new Date().toISOString(),
    });
  }
}

/**
 * Authenticate admin user using JWT token
 *
 * Expects: Authorization: Bearer <admin_jwt_token>
 */
export async function authenticateAdmin(
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> {
  try {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json({
        error: true,
        code: 'MISSING_TOKEN',
        message: 'Admin JWT token is required',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const token = authHeader.substring(7); // Remove "Bearer " prefix

    // Verify JWT token using admin-specific secret
    let payload: { adminId: string; email: string; role: AdminRole };
    try {
      payload = jwt.verify(token, config.jwtAdminSecret) as { adminId: string; email: string; role: AdminRole };
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        res.status(401).json({
          error: true,
          code: 'TOKEN_EXPIRED',
          message: 'Admin JWT token has expired',
          timestamp: new Date().toISOString(),
        });
        return;
      } else if (error instanceof jwt.JsonWebTokenError) {
        res.status(401).json({
          error: true,
          code: 'INVALID_TOKEN',
          message: 'Invalid admin JWT token',
          timestamp: new Date().toISOString(),
        });
        return;
      }
      throw error;
    }

    // Verify admin exists and is active
    const admin = await prisma.adminUser.findUnique({
      where: { id: payload.adminId },
    });

    if (!admin) {
      res.status(401).json({
        error: true,
        code: 'ADMIN_NOT_FOUND',
        message: 'Admin user not found',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    if (!(admin as any).is_active) {
      res.status(403).json({
        error: true,
        code: 'ADMIN_INACTIVE',
        message: 'Admin account has been deactivated',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    // Update last login timestamp (non-blocking)
    prisma.adminUser
      .update({
        where: { id: admin.id },
        data: { last_login_at: new Date() },
      })
      .catch((error) => {
        logger.error('Failed to update admin last login timestamp', { error, adminId: admin.id });
      });

    // Attach admin data to request
    req.admin = {
      id: admin.id,
      email: admin.email,
      role: admin.role as AdminRole,
    };

    next();
  } catch (error) {
    logger.error('Error in authenticateAdmin middleware', { error });
    res.status(500).json({
      error: true,
      code: 'AUTHENTICATION_ERROR',
      message: 'Internal authentication error',
      timestamp: new Date().toISOString(),
    });
  }
}
