/**
 * Subscription Check Middleware
 * Blocks access to module routes when company subscription is not active.
 * Super-admin users bypass all checks.
 */

import { Request, Response, NextFunction } from 'express';
import { getModels } from '../models';

// Paths that should not be blocked by subscription checks
const WHITELISTED_PATHS = [
  '/api/auth',
  '/api/super-admin',
  '/api/subscription-packages',
  '/api/company-subscriptions/checkout',
  '/api/company-subscriptions/my',
  '/api/company-subscriptions/my/access',
  '/api/company-subscriptions/webhook/stripe',
  '/api/company-subscriptions/webhook/razorpay',
  '/api/payment-gateway-config',
  '/api/currency-config',
  '/api/geo-currency',
  '/api/invoices/my',
  '/health',
];

function isWhitelisted(path: string): boolean {
  return WHITELISTED_PATHS.some((wp) => path.startsWith(wp));
}

/**
 * Maps subscription limit resource keys to Mongoose model names
 * and the company field used for querying.
 */
const RESOURCE_MODEL_MAP: Record<string, { model: string; companyField: string }> = {
  companies: { model: 'Company', companyField: 'userIds' },
  founders: { model: 'Founder', companyField: 'companyId' },
  employees: { model: 'Employee', companyField: 'companyId' },
  products: { model: 'Product', companyField: 'companyId' },
  icps: { model: 'ICP', companyField: 'companyId' },
  personas: { model: 'Persona', companyField: 'companyId' },
  competitors: { model: 'Competitor', companyField: 'companyId' },
  blogs: { model: 'Blog', companyField: 'companyId' },
  caseStudies: { model: 'CaseStudy', companyField: 'companyId' },
  testimonials: { model: 'Testimonial', companyField: 'companyId' },
  landingPages: { model: 'LandingPage', companyField: 'companyId' },
  salesScripts: { model: 'SalesScript', companyField: 'companyId' },
  salesCollateral: { model: 'SalesCollateral', companyField: 'companyId' },
  videoContent: { model: 'VideoContent', companyField: 'companyId' },
  audioContents: { model: 'AudioContent', companyField: 'companyId' },
  books: { model: 'Book', companyField: 'companyId' },
  campaigns: { model: 'AdCampaign', companyField: 'companyId' },
  adCampaigns: { model: 'AdCampaign', companyField: 'companyId' },
  prCampaigns: { model: 'PRCalendarEntry', companyField: 'companyId' },
  emailTemplates: { model: 'EmailTemplate', companyField: 'companyId' },
  courses: { model: 'Course', companyField: 'companyId' },
  events: { model: 'Event', companyField: 'companyId' },
  loyaltyProgrammes: { model: 'LoyaltyProgramme', companyField: 'companyId' },
  membershipPlans: { model: 'MembershipPlan', companyField: 'companyId' },
  referralProgrammes: { model: 'ReferralOffer', companyField: 'companyId' },
  referralTracking: { model: 'ReferralTracking', companyField: 'companyId' },
  sops: { model: 'SOP', companyField: 'companyId' },
  jobPostings: { model: 'JobPosting', companyField: 'companyId' },
  legalDocuments: { model: 'LegalDocument', companyField: 'companyId' },
  seoPages: { model: 'SEOPage', companyField: 'companyId' },
  gmbLocations: { model: 'GmbLocation', companyField: 'companyId' },
  websitePlanners: { model: 'WebsiteProject', companyField: 'companyId' },
  influencers: { model: 'Influencer', companyField: 'companyId' },
  imageGenerations: { model: 'ImageGeneration', companyField: 'companyId' },
  pitchDecks: { model: 'PitchDeck', companyField: 'companyId' },
  financialModels: { model: 'FinancialModel', companyField: 'companyId' },
  businessProfiles: { model: 'BusinessProfile', companyField: 'companyId' },
  newsletters: { model: 'Newsletter', companyField: 'companyId' },
  faqs: { model: 'FAQ', companyField: 'companyId' },
  hrAssets: { model: 'HRAsset', companyField: 'companyId' },
  stationeryItems: { model: 'BrandAsset', companyField: 'companyId' },
  introScripts: { model: 'IntroScript', companyField: 'companyId' },
  socialMediaPosts: { model: 'SocialMediaPost', companyField: 'companyId' },
  // Brand modules
  brandStrategies: { model: 'BrandStrategy', companyField: 'companyId' },
  visualIdentities: { model: 'VisualIdentity', companyField: 'companyId' },
  brandManuals: { model: 'BrandManual', companyField: 'companyId' },
  brandAssets: { model: 'BrandAsset', companyField: 'companyId' },
  // BrandKit
  brandKits: { model: 'BrandKit', companyField: 'companyId' },
  // WhatsApp Nurturing
  whatsappSequences: { model: 'WhatsAppSequence', companyField: 'companyId' },
  // Marketing modules
  guerrillaCampaigns: { model: 'GuerrillaCampaign', companyField: 'companyId' },
  interviewPreps: { model: 'InterviewPrep', companyField: 'companyId' },
  speakingScripts: { model: 'SpeakingScript', companyField: 'companyId' },
  moatAnalyses: { model: 'MoatAnalysis', companyField: 'companyId' },
  marketingCalendars: { model: 'MarketingCalendar', companyField: 'companyId' },
  marketingChannels: { model: 'MarketingChannel', companyField: 'companyId' },
  geoOptimizations: { model: 'GEOOptimization', companyField: 'companyId' },
  // Publishing
  magazineSponsorships: { model: 'MagazineSponsorship', companyField: 'companyId' },
  // Sales Performance
  commissionTracking: { model: 'CommissionTracker', companyField: 'companyId' },
  salesTargets: { model: 'SalesTarget', companyField: 'companyId' },
  salesPlaybooks: { model: 'SalesPlaybook', companyField: 'companyId' },
};

/**
 * Limit keys that name the same thing under two spellings.
 *
 * `campaigns` is the original ad-campaign limit; `adCampaigns` replaced it and
 * is the one the package builder exposes and `/api/ads` enforces. Packages
 * saved before the rename only carry the old key, so resolution falls back to
 * the alias before reaching FALLBACK_LIMITS.
 */
const LIMIT_ALIASES: Record<string, string> = {
  adCampaigns: 'campaigns',
  campaigns: 'adCampaigns',
};

/**
 * Fallback limits for keys that don't exist on older packages.
 * Must match the frontend FALLBACK_LIMITS in useSubscriptionLimit.ts.
 */
const FALLBACK_LIMITS: Record<string, number> = {
  founders: 3,
  employees: 10,
  icps: 5,
  personas: 10,
  competitors: 10,
  blogs: 20,
  caseStudies: 10,
  testimonials: 20,
  landingPages: 10,
  salesScripts: 10,
  salesCollateral: 10,
  videoContent: 20,
  books: 5,
  adCampaigns: 20,
  emailTemplates: 20,
  courses: 5,
  events: 10,
  loyaltyProgrammes: 3,
  membershipPlans: 5,
  referralProgrammes: 3,
  referralTracking: 100,
  sops: 10,
  jobPostings: 10,
  legalDocuments: 10,
  seoPages: 20,
  gmbLocations: 5,
  websitePlanners: 10,
  imageGenerations: 20,
  pitchDecks: 5,
  financialModels: 5,
  businessProfiles: 5,
  newsletters: 50,
  faqs: 50,
  hrAssets: 50,
  stationeryItems: 50,
  introScripts: 50,
  socialMediaPosts: 50,
  // Brand module defaults
  brandStrategies: 5,
  visualIdentities: 5,
  brandManuals: 3,
  brandAssets: 50,
  // BrandKit
  brandKits: 5,
  // WhatsApp Nurturing defaults
  whatsappSequences: 20,
  // Marketing module defaults
  guerrillaCampaigns: 20,
  interviewPreps: 20,
  speakingScripts: 20,
  moatAnalyses: 10,
  marketingCalendars: 20,
  marketingChannels: 20,
  geoOptimizations: 20,
  // Publishing defaults
  magazineSponsorships: 10,
  // Sales Performance defaults
  commissionTracking: 50,
  salesTargets: 20,
  salesPlaybooks: 10,
};

/**
 * Maps API route prefixes to subscription limit resource keys.
 * Used to auto-apply limit checks on POST routes.
 */
export const ROUTE_LIMIT_MAP: Record<string, string> = {
  // Foundation
  '/api/founders': 'founders',
  '/api/employees': 'employees',
  '/api/products': 'products',
  '/api/icps': 'icps',
  '/api/personas': 'personas',
  '/api/competitors': 'competitors',
  '/api/business-profiles': 'businessProfiles',
  // Content
  '/api/blogs': 'blogs',
  '/api/case-studies': 'caseStudies',
  '/api/testimonials': 'testimonials',
  '/api/seo-pages': 'seoPages',
  '/api/website-planners': 'websitePlanners',
  '/api/newsletters': 'newsletters',
  '/api/faqs': 'faqs',
  '/api/social-media': 'socialMediaPosts',
  // Brand
  '/api/brands': 'brandStrategies',
  '/api/brand-strategies': 'brandStrategies',
  '/api/visual-identities': 'visualIdentities',
  '/api/brand-manuals': 'brandManuals',
  '/api/brand-assets': 'brandAssets',
  '/api/stationery': 'stationeryItems',
  // BrandKit
  '/api/brandkits': 'brandKits',
  '/api/hr-assets': 'hrAssets',
  // Sales
  '/api/landing-pages': 'landingPages',
  '/api/whatsapp-nurturing': 'whatsappSequences',
  '/api/sales-scripts': 'salesScripts',
  '/api/sales-collateral': 'salesCollateral',
  '/api/video-content': 'videoContent',
  '/api/audio-contents': 'audioContents',
  '/api/books': 'books',
  '/api/intro-scripts': 'introScripts',
  // Marketing
  '/api/ads': 'adCampaigns',
  '/api/pr': 'prCampaigns',
  '/api/email-templates': 'emailTemplates',
  '/api/courses': 'courses',
  '/api/events': 'events',
  '/api/guerrilla-marketing': 'guerrillaCampaigns',
  '/api/interview-media-prep': 'interviewPreps',
  '/api/speaking-engagements': 'speakingScripts',
  '/api/moat-analysis': 'moatAnalyses',
  '/api/marketing-calendars': 'marketingCalendars',
  '/api/marketing-channels': 'marketingChannels',
  '/api/geo-optimization': 'geoOptimizations',
  '/api/influencers': 'influencers',
  '/api/gmb-locations': 'gmbLocations',
  // Programs
  '/api/loyalty-programmes': 'loyaltyProgrammes',
  '/api/membership-plans': 'membershipPlans',
  '/api/referral-programmes': 'referralProgrammes',
  '/api/referral-tracking': 'referralTracking',
  '/api/sops': 'sops',
  // Ops
  '/api/legal-documents': 'legalDocuments',
  // Publishing
  '/api/magazine-sponsorship': 'magazineSponsorships',
  // Funding
  '/api/pitch-decks': 'pitchDecks',
  '/api/financial-models': 'financialModels',
  '/api/investors': 'pitchDecks',
  '/api/funding-rounds': 'pitchDecks',
  '/api/presentations': 'pitchDecks',
  // Sales Performance
  '/api/commission-tracker': 'commissionTracking',
  '/api/sales-targets': 'salesTargets',
  '/api/sales-playbooks': 'salesPlaybooks',
  '/api/proposals-quotes': 'proposals',
  // Other
  '/api/job-postings': 'jobPostings',
  '/api/image-generations': 'imageGenerations',
};

/**
 * Sub-paths (relative to a ROUTE_LIMIT_MAP prefix) that create the limited
 * resource, for routers that expose several POST endpoints.
 *
 * Limits are enforced only on a router's root POST (`/`) or on a sub-path
 * listed here — never on every POST under the prefix, which would wrongly
 * block sibling endpoints such as `/api/sops/categories` or
 * `/api/sops/ai/generate-sop` once the SOP limit is reached.
 */
export const LIMIT_CREATE_SUBPATHS: Record<string, string[]> = {
  '/api/sops': ['/sops'],
  '/api/ads': ['/campaigns'],
  '/api/pr': ['/calendar'],
};

/** Human-readable resource names used in limit messages. */
const RESOURCE_LABELS: Record<string, string> = {
  sops: 'SOPs',
  adCampaigns: 'Ad Campaigns',
  campaigns: 'Campaigns',
  prCampaigns: 'PR Campaigns',
  founders: 'Founders',
  employees: 'Employees',
  products: 'Products',
  icps: 'ICPs',
  personas: 'Personas',
  competitors: 'Competitors',
  businessProfiles: 'Business Profiles',
  blogs: 'Blog Posts',
  caseStudies: 'Case Studies',
  testimonials: 'Testimonials',
  landingPages: 'Landing Pages',
  salesScripts: 'Sales Scripts',
  salesCollateral: 'Sales Collateral',
  videoContent: 'Video Content',
  books: 'Books',
  emailTemplates: 'Email Templates',
  courses: 'Courses',
  events: 'Events',
  loyaltyProgrammes: 'Loyalty Programmes',
  membershipPlans: 'Membership Plans',
  referralProgrammes: 'Referral Programmes',
  referralTracking: 'Referral Tracking',
  jobPostings: 'Job Postings',
  legalDocuments: 'Legal Documents',
  seoPages: 'SEO Pages',
  gmbLocations: 'GMB Locations',
  websitePlanners: 'Website Pages',
  influencers: 'Influencers',
  pitchDecks: 'Pitch Decks',
  financialModels: 'Financial Models',
  brandStrategies: 'Brand Strategies',
  visualIdentities: 'Visual Identities',
  brandManuals: 'Brand Manuals',
  brandAssets: 'Brand Assets',
  newsletters: 'Newsletters',
  faqs: 'FAQs',
  hrAssets: 'HR Assets',
  stationeryItems: 'Stationery Items',
  introScripts: 'Intro Scripts',
  socialMediaPosts: 'Social Media Posts',
};

/** Title-cases a camelCase resource key for resources with no explicit label. */
function resourceLabel(resource: string): string {
  return (
    RESOURCE_LABELS[resource] ??
    resource.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase()).trim()
  );
}

/**
 * True when a POST to `subPath` (the path remaining after the mount prefix)
 * is the create endpoint for `routePath`'s limited resource.
 */
export function isLimitedCreatePath(routePath: string, subPath: string): boolean {
  const normalized = subPath.replace(/\/+$/, '') || '/';
  if (normalized === '/') return true;
  return (LIMIT_CREATE_SUBPATHS[routePath] ?? []).includes(normalized);
}

/**
 * Resolve the subscription that governs a request for `companyId`.
 *
 * Subscriptions are stored per company, but a plan is bought once per account:
 * `POST /api/companies` only requires that *one* of the user's companies has an
 * active subscription, and it never writes a CompanySubscription for the new
 * company. Looking a second company up on its own therefore finds nothing, and
 * every create under it was rejected with 402 SUBSCRIPTION_REQUIRED — the
 * account is paid up, it just isn't the company the record hangs off.
 *
 * So: prefer the target company's own subscription, and otherwise fall back to
 * the best subscription across the user's other companies. The fallback only
 * supplies the plan/limits — callers still count records against the *target*
 * company, so per-company limits stay per-company.
 */
async function resolveSubscription(companyId: string, user?: { companyIds?: string[] }) {
  const { CompanySubscription } = getModels();

  const own = await CompanySubscription.findOne({ companyId });
  if (own) return own;

  const siblingIds = (user?.companyIds || []).filter((id) => String(id) !== String(companyId));
  if (siblingIds.length === 0) return null;

  const siblings = await CompanySubscription.find({ companyId: { $in: siblingIds } });
  if (siblings.length === 0) return null;

  // Prefer a usable plan; fall back to any so status-based messaging ("your
  // subscription is expired") still reports something truthful.
  return (
    siblings.find((s: any) => s.isLifetime) ||
    siblings.find((s: any) => s.status === 'active') ||
    siblings.find((s: any) => s.status === 'trial') ||
    siblings[0]
  );
}

/**
 * Middleware that checks company subscription status before allowing access.
 * If subscription is not active/trial, returns 402 Payment Required.
 * Super-admin users bypass all checks.
 */
export function requireActiveSubscription(req: Request, res: Response, next: NextFunction) {
  // Skip if no user (shouldn't happen after authenticate middleware, but be safe)
  if (!req.user) {
    next();
    return;
  }

  // Super-admin bypasses all checks
  if (req.user.role === 'super-admin') {
    next();
    return;
  }

  // Skip whitelisted paths
  if (isWhitelisted(req.path)) {
    next();
    return;
  }

  // Get company ID from params, body, or user's active company
  const companyId = req.params.companyId || req.body.companyId || req.user.companyIds?.[0];
  if (!companyId) {
    // No company context — allow through (will be caught by other middleware)
    next();
    return;
  }

  // ── Subscription check temporarily disabled ──
  // The subscription module is not yet implemented. All requests are allowed through.
  // Re-enable this block once the subscription/payment module is ready.
  //
  // (async () => {
  //   try {
  //     const subscription = await resolveSubscription(companyId, req.user);
  //
  //     if (!subscription) {
  //       res.status(402).json({
  //         error: 'SUBSCRIPTION_REQUIRED',
  //         message: 'No active subscription. Please select a plan to continue.',
  //       });
  //       return;
  //     }
  //
  //     if (subscription.isLifetime) {
  //       // Lifetime access — always allowed
  //       next();
  //       return;
  //     }
  //
  //     if (subscription.status === 'active' || subscription.status === 'trial') {
  //       // Check if trial has expired
  //       if (subscription.status === 'trial' && subscription.trialEndDate) {
  //         const now = new Date();
  //         if (now > new Date(subscription.trialEndDate)) {
  //           // Trial expired — update status
  //           subscription.status = 'expired';
  //           await subscription.save();
  //           res.status(402).json({
  //             error: 'TRIAL_EXPIRED',
  //             message: 'Your trial period has expired. Please upgrade to continue.',
  //           });
  //           return;
  //         }
  //       }
  //
  //       // Active subscription — allow
  //       next();
  //       return;
  //     }
  //
  //     // Subscription is expired, cancelled, suspended, or pending_payment
  //     res.status(402).json({
  //       error: 'SUBSCRIPTION_INACTIVE',
  //       message: `Your subscription is ${subscription.status}. Please renew to continue.`,
  //       status: subscription.status,
  //       endDate: subscription.endDate,
  //     });
  //   } catch (error) {
  //     console.error('Subscription check error:', error);
  //     // On error, allow through — don't block on DB failure
  //     next();
  //   }
  // })();

  // Allow all requests through while subscription module is not yet implemented
  next();
}

/**
 * Middleware factory that checks if a specific resource limit has been reached.
 * Actually counts records in the database and blocks creation with 403 if at limit.
 * Usage: checkSubscriptionLimit('founders')(req, res, next)
 */
export function checkSubscriptionLimit(resource: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    // Super-admin bypasses all checks
    if (req.user?.role === 'super-admin') {
      next();
      return;
    }

    const companyId = req.params.companyId || req.body.companyId || req.user?.companyIds?.[0];
    if (!companyId) {
      next();
      return;
    }

    try {
      const { SubscriptionPackage } = getModels();
      const subscription = await resolveSubscription(companyId, req.user);

      if (!subscription) {
        // ── Subscription required check temporarily disabled ──
        // Allow through while subscription module is not yet implemented.
        // Re-enable once subscription/payment module is ready.
        // res.status(402).json({
        //   error: 'SUBSCRIPTION_REQUIRED',
        //   message: 'No active subscription. Please select a plan to continue.',
        // });
        // return;
        next();
        return;
      }

      // Find the package to get limits
      const pkg = await SubscriptionPackage.findById(subscription.packageId);
      if (!pkg) {
        next(); // Can't check limits without package — allow
        return;
      }

      // Get the effective limit (override or package default, with fallback).
      // LIMIT_ALIASES covers keys that exist under two names: a package saved
      // before the newer key existed only carries the old one.
      const limits = pkg.limits?.toObject?.() || pkg.limits;
      const override = subscription.limitOverrides?.toObject?.() || subscription.limitOverrides;
      const alias = LIMIT_ALIASES[resource];
      const effectiveLimit = override?.[resource as keyof typeof limits]
        ?? limits[resource as keyof typeof limits]
        ?? (alias ? override?.[alias as keyof typeof limits] ?? limits[alias as keyof typeof limits] : undefined)
        ?? FALLBACK_LIMITS[resource];

      if (effectiveLimit === undefined || effectiveLimit === null) {
        next(); // No limit defined — allow
        return;
      }

      // A limit of 0 means unlimited
      if (effectiveLimit === 0) {
        next();
        return;
      }

      // Count current usage by querying the database
      const modelEntry = RESOURCE_MODEL_MAP[resource];
      if (modelEntry) {
        try {
          const models = getModels();
          const Model = models[modelEntry.model as keyof typeof models] as any;
          if (Model && typeof Model.countDocuments === 'function') {
            const count = await Model.countDocuments({ [modelEntry.companyField]: companyId });
            if (count >= effectiveLimit) {
              res.status(403).json({
                error: 'LIMIT_REACHED',
                message: `You have reached your plan limit for ${resourceLabel(resource)} (${count} of ${effectiveLimit}). Delete an existing record or upgrade your plan to add more.`,
                resource,
                currentCount: count,
                limit: effectiveLimit,
              });
              return;
            }
          }
        } catch (countError) {
          // If the model doesn't exist or count fails, don't block — just log
          console.warn(`[SubscriptionCheck] Could not count ${resource}:`, countError instanceof Error ? countError.message : countError);
        }
      }

      // Set limit info in response headers for frontend use
      res.setHeader('X-Subscription-Limit', String(effectiveLimit));
      res.setHeader('X-Subscription-Resource', resource);
      next();
    } catch (error) {
      console.error('Subscription limit check error:', error);
      next(); // Don't block on error
    }
  };
}