/**
 * Ads Management API Routes
 * Campaigns, Ads, Audiences, Budgets, Creative Assets, A/B Tests, AI Recommendations
 */

import express, { Request, Response } from 'express';
import { body, param, query, validationResult } from 'express-validator';
import { authenticate } from '../middleware/auth';
import { requirePermission } from '../middleware/permissions';
import { getModels } from '../models';
import { generateWithAI } from '../utils/aiProvider';
import multer from 'multer';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';

const router = express.Router();

// Multer for multipart file uploads (ad creative images)
const ADS_UPLOADS_DIR = path.resolve(process.cwd(), 'uploads', 'ad-creatives');
if (!fs.existsSync(ADS_UPLOADS_DIR)) {
  fs.mkdirSync(ADS_UPLOADS_DIR, { recursive: true });
}

const storage = multer.diskStorage({
  destination: (_req, _file, cb) => cb(null, ADS_UPLOADS_DIR),
  filename: (_req, file, cb) => {
    const ext = path.extname(file.originalname) || '.png';
    cb(null, `${uuidv4()}${ext}`);
  },
});

const upload = multer({
  storage,
  limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit
  fileFilter: (_req, file, cb) => {
    const allowed = ['image/png', 'image/jpeg', 'image/webp', 'image/gif', 'video/mp4', 'video/webm'];
    cb(null, allowed.includes(file.mimetype));
  },
});

router.use(authenticate);

// Helper: authorize company access
const authorizeCompany = (req: Request, companyId: string): boolean => {
  return req.user!.companyIds.includes(companyId) || req.user!.role === 'admin';
};

// Helper: handle errors with proper status codes (Mongoose ValidationError → 400, else → 500)
const handleError = (res: Response, error: any) => {
  if (error.name === 'ValidationError') {
    res.status(400).json({ error: error.message, details: Object.values(error.errors).map((e: any) => e.message) });
    return;
  }
  res.status(500).json({ error: error.message });
};

// ============================================
// CAMPAIGNS
// ============================================

router.get('/campaigns/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdCampaign } = getModels();
    const campaigns = await AdCampaign.find({ companyId }).sort({ createdAt: -1 });
    res.json(campaigns);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/campaigns/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdCampaign } = getModels();
    const campaign = await AdCampaign.findById(id);
    if (!campaign) { res.status(404).json({ error: 'Campaign not found' }); return; }
    if (!authorizeCompany(req, (campaign as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(campaign);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/campaigns', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('name').notEmpty().withMessage('Campaign name is required'),
  body('goal').notEmpty().withMessage('Goal is required'),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdCampaign } = getModels();
    const campaign = await AdCampaign.create(req.body);
    res.status(201).json(campaign);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.put('/campaigns/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdCampaign } = getModels();
    const campaign = await AdCampaign.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!campaign) { res.status(404).json({ error: 'Campaign not found' }); return; }
    res.json(campaign);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/campaigns/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdCampaign } = getModels();
    const campaign = await AdCampaign.findByIdAndDelete(id);
    if (!campaign) { res.status(404).json({ error: 'Campaign not found' }); return; }
    res.json({ message: 'Campaign deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// ADS
// ============================================

router.get('/ads/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { Ad } = getModels();
    const ads = await Ad.find({ companyId }).sort({ createdAt: -1 });
    res.json(ads);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/ads/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { Ad } = getModels();
    const ad = await Ad.findById(id);
    if (!ad) { res.status(404).json({ error: 'Ad not found' }); return; }
    if (!authorizeCompany(req, (ad as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(ad);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/ads', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('campaignId').notEmpty().withMessage('Campaign ID is required'),
  body('name').notEmpty().withMessage('Ad name is required'),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { Ad } = getModels();
    const ad = await Ad.create(req.body);
    res.status(201).json(ad);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.put('/ads/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { Ad } = getModels();
    const ad = await Ad.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!ad) { res.status(404).json({ error: 'Ad not found' }); return; }
    res.json(ad);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/ads/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { Ad } = getModels();
    const ad = await Ad.findByIdAndDelete(id);
    if (!ad) { res.status(404).json({ error: 'Ad not found' }); return; }
    res.json({ message: 'Ad deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// AUDIENCES
// ============================================

router.get('/audiences/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdAudience } = getModels();
    const audiences = await AdAudience.find({ companyId }).sort({ createdAt: -1 });
    res.json(audiences);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/audiences/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdAudience } = getModels();
    const audience = await AdAudience.findById(id);
    if (!audience) { res.status(404).json({ error: 'Audience not found' }); return; }
    if (!authorizeCompany(req, (audience as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(audience);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/audiences', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('campaignId').notEmpty().withMessage('Campaign ID is required'),
  body('name').notEmpty().withMessage('Audience name is required'),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdAudience } = getModels();
    const audience = await AdAudience.create(req.body);
    res.status(201).json(audience);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.put('/audiences/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdAudience } = getModels();
    const audience = await AdAudience.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!audience) { res.status(404).json({ error: 'Audience not found' }); return; }
    res.json(audience);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/audiences/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdAudience } = getModels();
    const audience = await AdAudience.findByIdAndDelete(id);
    if (!audience) { res.status(404).json({ error: 'Audience not found' }); return; }
    res.json({ message: 'Audience deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// BUDGETS
// ============================================

router.get('/budgets/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdBudget } = getModels();
    const budgets = await AdBudget.find({ companyId }).sort({ createdAt: -1 });
    res.json(budgets);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/budgets/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdBudget } = getModels();
    const budget = await AdBudget.findById(id);
    if (!budget) { res.status(404).json({ error: 'Budget not found' }); return; }
    if (!authorizeCompany(req, (budget as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(budget);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/budgets', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('campaignId').notEmpty().withMessage('Campaign ID is required'),
  body('dailyBudget').isNumeric().withMessage('Daily budget is required'),
  body('totalBudget').isNumeric().withMessage('Total budget is required'),
  body('periodStart').notEmpty().withMessage('Start date is required'),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdBudget } = getModels();
    const budget = await AdBudget.create(req.body);
    res.status(201).json(budget);
  } catch (error: any) {
    // Return 400 for Mongoose validation errors instead of 500
    if (error.name === 'ValidationError') {
      res.status(400).json({ error: error.message, details: Object.values(error.errors).map((e: any) => e.message) });
      return;
    }
    handleError(res, error);
  }
});

router.put('/budgets/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdBudget } = getModels();
    const budget = await AdBudget.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!budget) { res.status(404).json({ error: 'Budget not found' }); return; }
    res.json(budget);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/budgets/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdBudget } = getModels();
    const budget = await AdBudget.findByIdAndDelete(id);
    if (!budget) { res.status(404).json({ error: 'Budget not found' }); return; }
    res.json({ message: 'Budget deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// CREATIVE ASSETS
// ============================================

router.get('/creative-assets/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdCreativeAsset } = getModels();
    const assets = await AdCreativeAsset.find({ companyId }).sort({ createdAt: -1 });
    res.json(assets);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/creative-assets/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdCreativeAsset } = getModels();
    const asset = await AdCreativeAsset.findById(id);
    if (!asset) { res.status(404).json({ error: 'Asset not found' }); return; }
    if (!authorizeCompany(req, (asset as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(asset);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/creative-assets', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('name').notEmpty().withMessage('Asset name is required'),
  body('type').notEmpty().withMessage('Asset type is required'),
  body('url').optional(),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdCreativeAsset } = getModels();
    const asset = await AdCreativeAsset.create(req.body);
    res.status(201).json(asset);
  } catch (error: any) {
    // Return 400 for Mongoose validation errors instead of 500
    if (error.name === 'ValidationError') {
      res.status(400).json({ error: error.message, details: Object.values(error.errors).map((e: any) => e.message) });
      return;
    }
    handleError(res, error);
  }
});

router.put('/creative-assets/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdCreativeAsset } = getModels();
    const asset = await AdCreativeAsset.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!asset) { res.status(404).json({ error: 'Asset not found' }); return; }
    res.json(asset);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/creative-assets/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdCreativeAsset } = getModels();
    const asset = await AdCreativeAsset.findByIdAndDelete(id);
    if (!asset) { res.status(404).json({ error: 'Asset not found' }); return; }
    res.json({ message: 'Asset deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// AI IMAGE PROMPT GENERATION
// ============================================

router.post(
  '/creative-assets/generate-prompt',
  requirePermission('ads', 'ai-generate'),
  [
    body('companyId').notEmpty().withMessage('Company ID is required'),
    body('platform').notEmpty().withMessage('Platform is required'),
    body('format').notEmpty().withMessage('Format is required'),
  ],
  async (req: Request, res: Response) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }

      const { companyId, campaignId, platform, format } = req.body;
      if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }

      const { AdCampaign, AdAudience } = getModels();

      // Fetch context data
      const campaignFilter = campaignId ? { companyId, _id: campaignId } : { companyId };
      const [campaigns, audiences] = await Promise.all([
        AdCampaign.find(campaignFilter).select('name goal status platforms headline description cta totalBudget').lean(),
        campaignId
          ? AdAudience.find({ companyId, campaignId }).select('name type demographics interests behaviors estimatedSize').lean()
          : AdAudience.find({ companyId }).select('name type demographics interests behaviors estimatedSize').lean(),
      ]);

      // Build context
      const contextLines: string[] = [];
      contextLines.push(`Platform: ${platform}`);
      contextLines.push(`Ad Format: ${format}`);
      if (campaigns.length > 0) {
        contextLines.push(`\nCampaigns:`);
        campaigns.forEach((c: any) => {
          contextLines.push(`  "${c.name}" | Goal: ${c.goal || 'N/A'} | Status: ${c.status} | Platforms: ${(c.platforms || []).join(', ')} | Budget: $${c.totalBudget || 0}`);
        });
      }
      if (audiences.length > 0) {
        contextLines.push(`\nTarget Audiences:`);
        audiences.slice(0, 10).forEach((a: any) => {
          contextLines.push(`  "${a.name}" | Type: ${a.type} | Age: ${a.demographics?.ageMin || 18}-${a.demographics?.ageMax || 65} | Interests: ${(a.interests || []).slice(0, 5).join(', ')} | Locations: ${(a.demographics?.locations || []).slice(0, 3).join(', ')}`);
        });
      }
      const dataContext = contextLines.join('\n');

      const systemPrompt = `You are an expert ad creative director specializing in visual design for digital advertising. Given campaign and audience context, generate detailed image generation prompts that can be used in Midjourney, DALL-E, or Stable Diffusion to create ad visuals.

Each prompt must be highly detailed and include:
- Visual scene description (subject, composition, mood, lighting)
- Style direction (photography style, illustration style, 3D render, etc.)
- Colour palette (specific hex or named colors)
- Text/CTA overlay suggestion (what text goes on the banner and where)
- Negative prompt (what to avoid)

Return a JSON array of 3-5 prompt objects. Each object must have:
{
  "prompt": "Detailed image generation prompt (200+ words, highly specific)",
  "style": "Brief style label (e.g., 'Cinematic Photography', 'Flat Illustration', '3D Render')",
  "colorPalette": ["#hex1", "#hex2", "#hex3", "#hex4"],
  "ctaOverlay": "Suggested CTA text and placement (e.g., 'Shop Now - bottom center')",
  "negativePrompt": "Things to avoid in generation"
}

Tailor the prompts to the specified platform (${platform}) and format (${format}).
When asked for JSON, respond with ONLY valid JSON — no markdown fences, no explanation before or after.`;;

      const analysisPrompt = `Generate ad creative image prompts for the following:\n\n${dataContext}`;

      const userId = req.user?._id?.toString() || req.user?.id;
      const result = await generateWithAI(analysisPrompt, systemPrompt, 4000, undefined, undefined, undefined, undefined, userId);

      console.log(`[Ads/GeneratePrompt] AI response received — provider: ${result.provider}, content length: ${result.content.length}`);
      console.log(`[Ads/GeneratePrompt] Raw response preview: ${result.content.substring(0, 500)}`);

      // Parse response with robust handling for various AI response formats
      let prompts = parseJsonArray<GeneratedPrompt>(result.content, [
        { key: 'prompt', required: true },
        { key: 'style' },
        { key: 'colorPalette', isArray: true },
        { key: 'ctaOverlay' },
        { key: 'negativePrompt' },
      ]);

      // Fallback: try alternate field names some AI models use
      if (prompts.length === 0) {
        console.log(`[Ads/GeneratePrompt] Primary parse returned 0, trying alternate field names...`);
        // Try each alternate prompt key as the required field
        const alternateKeys = ['image_prompt', 'text', 'description', 'content', 'imagePrompt'];
        for (const altKey of alternateKeys) {
          prompts = parseJsonArray<GeneratedPrompt>(result.content, [
            { key: altKey, required: true },
            { key: 'prompt' },
            { key: 'style' },
            { key: 'colorPalette', isArray: true },
            { key: 'colors', isArray: true },
            { key: 'ctaOverlay' },
            { key: 'cta' },
            { key: 'negativePrompt' },
            { key: 'negative_prompt' },
          ]);
          if (prompts.length > 0) {
            // Remap: use the alternate key value as 'prompt' if 'prompt' is empty
            prompts = prompts.map(p => ({
              ...p,
              prompt: (p as any)[altKey] || p.prompt,
              colorPalette: p.colorPalette.length > 0 ? p.colorPalette : ((p as any).colors || []),
              ctaOverlay: p.ctaOverlay || (p as any).cta || '',
              negativePrompt: p.negativePrompt || (p as any).negative_prompt || '',
            }));
            console.log(`[Ads/GeneratePrompt] Alternate key "${altKey}" worked, got ${prompts.length} prompts`);
            break;
          }
        }
      }

      // Last fallback: try to extract any array of objects and treat first string field as prompt
      if (prompts.length === 0) {
        console.log(`[Ads/GeneratePrompt] All parse attempts returned 0, trying raw object extraction...`);
        let rawContent = result.content.trim();
        rawContent = rawContent.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
        const firstBracket = rawContent.indexOf('[');
        if (firstBracket >= 0) rawContent = rawContent.substring(firstBracket);
        const jsonMatch = rawContent.match(/\[[\s\S]*\]/);
        if (jsonMatch) {
          try {
            const rawParsed = JSON.parse(jsonMatch[0]);
            if (Array.isArray(rawParsed)) {
              prompts = rawParsed.filter((item: any) => item && typeof item === 'object').map((item: any) => {
                // Find the longest string field and use it as the prompt
                let promptText = '';
                for (const val of Object.values(item)) {
                  if (typeof val === 'string' && val.length > promptText.length) promptText = val;
                }
                return {
                  prompt: promptText,
                  style: item.style || '',
                  colorPalette: Array.isArray(item.colorPalette) || Array.isArray(item.colors) ? (item.colorPalette || item.colors) : [],
                  ctaOverlay: item.ctaOverlay || item.cta || '',
                  negativePrompt: item.negativePrompt || item.negative_prompt || '',
                } as GeneratedPrompt;
              }).filter((p: GeneratedPrompt) => p.prompt.length > 20);
            }
          } catch { /* ignore */ }
        }
        if (prompts.length > 0) {
          console.log(`[Ads/GeneratePrompt] Raw extraction found ${prompts.length} prompts`);
        }
      }

      if (prompts.length === 0) {
        console.log(`[Ads/GeneratePrompt] All parse attempts failed — returning empty with warning`);
      }

      res.json({ prompts, count: prompts.length, provider: result.provider });
    } catch (error: any) {
      console.error('[Ads/GeneratePrompt] AI prompt generation failed:', error.message);
      res.status(500).json({ error: `Prompt generation failed: ${error.message}` });
    }
  }
);

// ============================================
// A/B TESTS
// ============================================

router.get('/ab-tests/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdABTest } = getModels();
    const tests = await AdABTest.find({ companyId }).sort({ createdAt: -1 });
    res.json(tests);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/ab-tests/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdABTest } = getModels();
    const test = await AdABTest.findById(id);
    if (!test) { res.status(404).json({ error: 'A/B Test not found' }); return; }
    if (!authorizeCompany(req, (test as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(test);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/ab-tests', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('campaignId').notEmpty().withMessage('Campaign ID is required'),
  body('name').notEmpty().withMessage('Test name is required'),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdABTest } = getModels();
    const test = await AdABTest.create(req.body);
    res.status(201).json(test);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.put('/ab-tests/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdABTest } = getModels();
    const test = await AdABTest.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!test) { res.status(404).json({ error: 'A/B Test not found' }); return; }
    res.json(test);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/ab-tests/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdABTest } = getModels();
    const test = await AdABTest.findByIdAndDelete(id);
    if (!test) { res.status(404).json({ error: 'A/B Test not found' }); return; }
    res.json({ message: 'A/B Test deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// AI RECOMMENDATIONS
// ============================================

router.get('/recommendations/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdAIRecommendation } = getModels();
    const recommendations = await AdAIRecommendation.find({ companyId }).sort({ createdAt: -1 });
    res.json(recommendations);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.get('/recommendations/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdAIRecommendation } = getModels();
    const rec = await AdAIRecommendation.findById(id);
    if (!rec) { res.status(404).json({ error: 'Recommendation not found' }); return; }
    if (!authorizeCompany(req, (rec as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    res.json(rec);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.post('/recommendations', requirePermission('ads', 'create'), [
  body('companyId').notEmpty().withMessage('Company ID is required'),
  body('category').notEmpty().withMessage('Category is required'),
  body('title').notEmpty().withMessage('Title is required'),
  body('description').notEmpty().withMessage('Description is required'),
], async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }
    const { companyId } = req.body;
    if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }
    const { AdAIRecommendation } = getModels();
    const rec = await AdAIRecommendation.create(req.body);
    res.status(201).json(rec);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.put('/recommendations/:id', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdAIRecommendation } = getModels();
    const rec = await AdAIRecommendation.findByIdAndUpdate(id, req.body, { new: true, runValidators: true });
    if (!rec) { res.status(404).json({ error: 'Recommendation not found' }); return; }
    res.json(rec);
  } catch (error: any) {
    handleError(res, error);
  }
});

router.delete('/recommendations/:id', requirePermission('ads', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { AdAIRecommendation } = getModels();
    const rec = await AdAIRecommendation.findByIdAndDelete(id);
    if (!rec) { res.status(404).json({ error: 'Recommendation not found' }); return; }
    res.json({ message: 'Recommendation deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

// PATCH for recommendation status updates
router.patch('/recommendations/:id/status', requirePermission('ads', 'edit'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { status } = req.body;
    if (!['viewed', 'applied', 'dismissed'].includes(status)) {
      res.status(400).json({ error: 'Invalid status. Must be: viewed, applied, or dismissed' });
      return;
    }
    const { AdAIRecommendation } = getModels();
    const rec = await AdAIRecommendation.findByIdAndUpdate(id, { status }, { new: true });
    if (!rec) { res.status(404).json({ error: 'Recommendation not found' }); return; }
    res.json(rec);
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// AI-POWERED ANALYSIS
// ============================================

const VALID_REC_CATEGORIES = ['budget', 'audience', 'creative', 'targeting', 'bidding', 'timing', 'platform'];
const VALID_REC_PRIORITIES = ['low', 'medium', 'high', 'critical'];

function parseRecommendations(rawContent: string): Array<Record<string, any>> {
  let content = rawContent.trim();
  // Strip markdown fences
  content = content.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
  // Extract JSON array
  const jsonMatch = content.match(/\[[\s\S]*\]/);
  if (!jsonMatch) return [];
  let parsed: any[];
  try {
    parsed = JSON.parse(jsonMatch[0]);
  } catch {
    return [];
  }
  if (!Array.isArray(parsed)) return [];
  return parsed.filter(item => {
    if (!item.category || !item.title || !item.description) return false;
    if (!VALID_REC_CATEGORIES.includes(item.category)) item.category = 'creative';
    if (!VALID_REC_PRIORITIES.includes(item.priority)) item.priority = 'medium';
    if (typeof item.confidence !== 'number') item.confidence = 50;
    item.confidence = Math.max(0, Math.min(100, item.confidence));
    return true;
  });
}

/**
 * Robust JSON array parser for AI responses.
 * Handles: markdown fences, thinking/reasoning text, nested objects,
 * GLM-style reasoning prefixes, and multiple JSON candidates.
 */
function parseJsonArray<T>(rawContent: string, fieldSpecs: Array<{ key: string; required?: boolean; isArray?: boolean }>): T[] {
  let content = rawContent.trim();

  // Strip markdown code fences
  content = content.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();

  // Strip GLM-style thinking/reasoning prefixes (numbered reasoning steps before JSON)
  content = content.replace(/^\d+\.\s+\*\*[^*]+\*\*:?[\s\S]*?(?=\[)/gm, '').trim();

  // Strip any text before the first [
  const firstBracket = content.indexOf('[');
  if (firstBracket > 0) {
    content = content.substring(firstBracket);
  }

  let parsed: any[] | null = null;

  // Attempt 1: parse the entire content as JSON
  try {
    const result = JSON.parse(content);
    if (Array.isArray(result)) { parsed = result; }
  } catch { /* not raw JSON, continue */ }

  // Attempt 2: find all [ positions and try parsing from each (right to left for innermost)
  if (!parsed) {
    const bracketPositions: number[] = [];
    for (let i = 0; i < content.length; i++) {
      if (content[i] === '[') bracketPositions.push(i);
    }
    for (let i = bracketPositions.length - 1; i >= 0; i--) {
      const startIdx = bracketPositions[i];
      let depth = 0;
      let endIdx = -1;
      for (let j = startIdx; j < content.length; j++) {
        if (content[j] === '[') depth++;
        if (content[j] === ']') depth--;
        if (depth === 0) { endIdx = j; break; }
      }
      if (endIdx === -1) continue;
      const candidate = content.substring(startIdx, endIdx + 1);
      try {
        const result = JSON.parse(candidate);
        if (Array.isArray(result)) { parsed = result; break; }
      } catch { /* try next position */ }
    }
  }

  // Attempt 3: greedy regex fallback
  if (!parsed) {
    const jsonMatch = content.match(/\[[\s\S]*\]/);
    if (jsonMatch) {
      try { parsed = JSON.parse(jsonMatch[0]); } catch { /* ignore */ }
    }
  }

  if (!parsed || !Array.isArray(parsed)) return [];

  // Map fields according to spec
  return parsed.map((item: any) => {
    if (!item || typeof item !== 'object') return null;
    const result: any = {};
    for (const spec of fieldSpecs) {
      if (spec.isArray) {
        result[spec.key] = Array.isArray(item[spec.key]) ? item[spec.key] : [];
      } else {
        result[spec.key] = item[spec.key] || '';
      }
    }
    // Skip items missing required fields
    for (const spec of fieldSpecs) {
      if (spec.required && !result[spec.key]) return null;
    }
    return result as T;
  }).filter((x): x is T => x !== null);
}

interface GeneratedPrompt {
  prompt: string;
  style: string;
  colorPalette: string[];
  ctaOverlay: string;
  negativePrompt: string;
}

function buildAnalysisContext(data: {
  campaigns: any[]; ads: any[]; audiences: any[]; budgets: any[];
  creativeAssets: any[]; abTests: any[]; existingRecs: any[];
}): string {
  const { campaigns, ads, audiences, budgets, creativeAssets, abTests, existingRecs } = data;
  const lines: string[] = [];

  // Campaigns
  lines.push(`Campaigns (${campaigns.length} total):`);
  if (campaigns.length === 0) {
    lines.push('  No campaigns found.');
  } else {
    const active = campaigns.filter(c => c.status === 'active').length;
    const paused = campaigns.filter(c => c.status === 'paused').length;
    lines.push(`  Active: ${active}, Paused: ${paused}, Other: ${campaigns.length - active - paused}`);
    campaigns.forEach(c => {
      lines.push(`  [${c._id}] "${c.name}" | Goal: ${c.goal || 'N/A'} | Status: ${c.status} | Platforms: ${(c.platforms || []).join(', ') || 'N/A'} | Budget: $${c.totalBudget || 0} | ${c.startDate || 'N/A'} to ${c.endDate || 'ongoing'}`);
    });
  }

  // Ads
  lines.push(`\nAds (${ads.length} total):`);
  if (ads.length === 0) {
    lines.push('  No ads found.');
  } else {
    ads.forEach(a => {
      const perf = a.performance?.impressions > 0
        ? `Impressions: ${a.performance.impressions} | Clicks: ${a.performance.clicks} | CTR: ${a.performance.ctr}% | Conversions: ${a.performance.conversions} | CPC: $${a.performance.costPerClick || 0} | Spend: $${a.performance.spend || 0}`
        : 'No performance data';
      lines.push(`  [${a._id}] "${a.name}" | Campaign: ${a.campaignId || 'N/A'} | Platform: ${a.platform || 'N/A'} | CTA: ${a.cta || 'N/A'} | Status: ${a.status} | ${perf}`);
    });
  }

  // Audiences
  lines.push(`\nAudiences (${audiences.length} total):`);
  if (audiences.length === 0) {
    lines.push('  No audiences found.');
  } else {
    audiences.forEach(a => {
      lines.push(`  [${a._id}] "${a.name}" | Type: ${a.type || 'N/A'} | Age: ${a.demographics?.ageMin || 18}-${a.demographics?.ageMax || 65} | Locations: ${(a.demographics?.locations || []).length} | Interests: ${(a.interests || []).length} | Behaviors: ${(a.behaviors || []).length} | Est. Size: ${a.estimatedSize || 'N/A'}`);
    });
  }

  // Budgets
  lines.push(`\nBudgets (${budgets.length} total):`);
  if (budgets.length === 0) {
    lines.push('  No budgets found.');
  } else {
    const totalDaily = budgets.reduce((s: number, b: any) => s + (b.dailyBudget || 0), 0);
    const totalBudget = budgets.reduce((s: number, b: any) => s + (b.totalBudget || 0), 0);
    lines.push(`  Total daily: $${totalDaily} | Total allocated: $${totalBudget}`);
    budgets.forEach(b => {
      const allocs = (b.allocations || []).map((a: any) => `${a.platform}: ${a.percentage}%`).join(', ');
      lines.push(`  Campaign: ${b.campaignId || 'N/A'} | Daily: $${b.dailyBudget} | Total: $${b.totalBudget} | Bid: ${b.bidStrategy || 'N/A'} | Pacing: ${b.pacing || 'N/A'}${allocs ? ` | Allocation: ${allocs}` : ''}`);
    });
  }

  // Creative Assets
  lines.push(`\nCreative Assets (${creativeAssets.length} total):`);
  if (creativeAssets.length === 0) {
    lines.push('  No creative assets found.');
  } else {
    const approved = creativeAssets.filter(a => a.status === 'approved').length;
    const draft = creativeAssets.filter(a => a.status === 'draft').length;
    lines.push(`  Approved: ${approved}, Draft: ${draft}, Other: ${creativeAssets.length - approved - draft}`);
    creativeAssets.slice(0, 30).forEach(a => {
      lines.push(`  [${a._id}] "${a.name}" | Type: ${a.type || 'N/A'} | CTA: ${a.cta || 'none'} | Status: ${a.status || 'N/A'}`);
    });
    if (creativeAssets.length > 30) lines.push(`  ... and ${creativeAssets.length - 30} more`);
  }

  // A/B Tests
  lines.push(`\nA/B Tests (${abTests.length} total):`);
  if (abTests.length === 0) {
    lines.push('  No A/B tests found.');
  } else {
    abTests.forEach(t => {
      const variants = (t.variants || []).map((v: any) => `${v.name}: CTR ${v.ctr || 0}%, Conv ${v.conversionRate || 0}%`).join(', ');
      lines.push(`  [${t._id}] "${t.name}" | Metric: ${t.metric || 'N/A'} | Status: ${t.status} | Winner: ${t.winnerVariantId || 'TBD'} | Variants: ${variants || 'N/A'}`);
    });
  }

  // Existing recommendations (for dedup context)
  lines.push(`\nExisting Recommendations (${existingRecs.length} total):`);
  if (existingRecs.length === 0) {
    lines.push('  No existing recommendations.');
  } else {
    existingRecs.forEach(r => {
      lines.push(`  [${r.category}] "${r.title}" | Status: ${r.status}`);
    });
  }

  return lines.join('\n');
}

router.post(
  '/recommendations/analyze',
  requirePermission('ads', 'manage'),
  [body('companyId').notEmpty().withMessage('Company ID is required')],
  async (req: Request, res: Response) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) { res.status(400).json({ errors: errors.array() }); return; }

      const { companyId, campaignId } = req.body;
      if (!authorizeCompany(req, companyId)) { res.status(403).json({ error: 'Access denied' }); return; }

      const { AdCampaign, Ad, AdAudience, AdBudget, AdCreativeAsset, AdABTest, AdAIRecommendation } = getModels();

      // Build filter: scope to specific campaign or fetch all
      const campaignFilter = campaignId ? { companyId, _id: campaignId } : { companyId };
      const adFilter = campaignId ? { companyId, campaignId } : { companyId };
      const audienceFilter = campaignId ? { companyId, campaignId } : { companyId };
      const budgetFilter = campaignId ? { companyId, campaignId } : { companyId };
      const abTestFilter = campaignId ? { companyId, campaignId } : { companyId };

      // Fetch data in parallel (creative assets are not campaign-scoped, always fetch all)
      const [campaigns, ads, audiences, budgets, creativeAssets, abTests, existingRecs] = await Promise.all([
        AdCampaign.find(campaignFilter).select('name goal status platforms startDate endDate totalBudget currency').lean(),
        Ad.find(adFilter).select('name campaignId headline description cta platform objective status performance').lean(),
        AdAudience.find(audienceFilter).select('name type demographics interests behaviors estimatedSize campaignId').lean(),
        AdBudget.find(budgetFilter).select('campaignId audienceId dailyBudget totalBudget bidStrategy pacing allocations').lean(),
        AdCreativeAsset.find({ companyId }).select('name type cta status').lean(),
        AdABTest.find(abTestFilter).select('name metric status winnerVariantId variants campaignId').lean(),
        AdAIRecommendation.find({ companyId, status: { $in: ['new', 'viewed'] } }).select('title category status').lean(),
      ]);

      // Check if there's any data to analyze
      const totalEntities = campaigns.length + ads.length + audiences.length + budgets.length + creativeAssets.length + abTests.length;
      if (totalEntities === 0) {
        res.status(400).json({ error: 'No ad data found for analysis. Create campaigns, ads, and budgets first.' });
        return;
      }

      // Build the analysis prompt
      const dataContext = buildAnalysisContext({ campaigns, ads, audiences, budgets, creativeAssets, abTests, existingRecs });

      const systemPrompt = `You are an expert digital advertising analyst. Given summarized data about a company's ad campaigns, ads, audiences, budgets, creative assets, and A/B tests, generate actionable optimization recommendations.

Analyze the data for:
- Budget allocation inefficiencies (overspend, underspend, poor bid strategy)
- Audience targeting gaps (narrow reach, missing demographics, unused lookalike audiences)
- Creative performance issues (low-CTR headlines, weak CTAs, untested variants)
- Bidding strategy improvements (manual vs automated, wrong objective for bid type)
- Timing and scheduling optimization (dayparting, seasonal adjustments)
- Platform-specific recommendations (channel misalignment, cross-platform opportunities)

Return a JSON array of recommendation objects. Each object must have exactly these fields:
{
  "category": "budget" | "audience" | "creative" | "targeting" | "bidding" | "timing" | "platform",
  "priority": "low" | "medium" | "high" | "critical",
  "title": "Short actionable title (max 100 chars)",
  "description": "Detailed description of the recommendation (1-3 sentences)",
  "rationale": "Why this recommendation matters, backed by data points from the input",
  "expectedImpact": "Expected quantified improvement (e.g., '15-25% CTR increase')",
  "confidence": <number 0-100>,
  "campaignId": "<id of the most relevant campaign, or null>",
  "relatedEntityType": "campaign" | "ad" | "audience" | "budget" | "creative" | null,
  "relatedEntityId": "<id of the most relevant entity, or null>"
}

Generate 5-10 recommendations. Prioritize the highest-impact changes first.
Avoid duplicating any recommendations listed in the "Existing Recommendations" section.
When asked for JSON, respond with ONLY valid JSON — no markdown fences, no explanation before or after.`;

      const analysisPrompt = campaignId
        ? `Analyze the following ad data for a specific campaign and generate focused optimization recommendations for that campaign:\n\n${dataContext}`
        : `Analyze the following ad data and generate optimization recommendations:\n\n${dataContext}`;

      const userId = req.user?._id?.toString() || req.user?.id;
      const result = await generateWithAI(analysisPrompt, systemPrompt, 6000, undefined, undefined, undefined, undefined, userId);

      // Parse the AI response
      const parsed = parseRecommendations(result.content);
      if (parsed.length === 0) {
        res.json({
          recommendations: [],
          count: 0,
          provider: result.provider,
          warning: 'AI response could not be parsed as recommendations',
        });
        return;
      }

      // Deduplicate against existing recommendations by normalized title
      const existingSlugs = new Set(existingRecs.map((r: any) => (r.title || '').toLowerCase().replace(/[^a-z0-9]/g, '')));
      const newRecs = parsed.filter(rec => {
        const slug = (rec.title || '').toLowerCase().replace(/[^a-z0-9]/g, '');
        return !existingSlugs.has(slug);
      });

      if (newRecs.length === 0) {
        res.json({
          recommendations: [],
          count: 0,
          provider: result.provider,
          warning: 'All generated recommendations already exist',
        });
        return;
      }

      // Create recommendation documents
      const docsToCreate = newRecs.map(rec => ({
        companyId,
        category: rec.category,
        priority: rec.priority || 'medium',
        title: rec.title,
        description: rec.description,
        rationale: rec.rationale || '',
        expectedImpact: rec.expectedImpact || '',
        confidence: rec.confidence || 50,
        status: 'new' as const,
        source: 'ai' as const,
        campaignId: rec.campaignId || undefined,
        relatedEntityId: rec.relatedEntityId || undefined,
        relatedEntityType: rec.relatedEntityType || undefined,
      }));

      const created = await AdAIRecommendation.insertMany(docsToCreate);

      res.status(201).json({
        recommendations: created,
        count: created.length,
        provider: result.provider,
      });
    } catch (error: any) {
      console.error('[Ads/Analyze] AI analysis failed:', error.message);
      res.status(500).json({ error: `AI analysis failed: ${error.message}` });
    }
  }
);

// ============================================
// HIERARCHY ENDPOINTS (for nested navigation)
// ============================================

// GET /ads/by-campaign/:campaignId - Get all ads for a specific campaign
router.get('/ads/by-campaign/:campaignId', async (req: Request, res: Response) => {
  try {
    const { campaignId } = req.params;
    const { Ad, AdCampaign } = getModels();

    // First verify the campaign exists and user has access
    const campaign = await AdCampaign.findById(campaignId);
    if (!campaign) { res.status(404).json({ error: 'Campaign not found' }); return; }
    if (!authorizeCompany(req, (campaign as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }

    // Get all ads for this campaign
    const ads = await Ad.find({ campaignId }).sort({ createdAt: -1 });
    res.json(ads);
  } catch (error: any) {
    handleError(res, error);
  }
});

// GET /ads/by-adset/:adSetId - Get all ads for a specific ad set
router.get('/ads/by-adset/:adSetId', async (req: Request, res: Response) => {
  try {
    const { adSetId } = req.params;
    const { Ad, AdAudience } = getModels();

    // First verify the ad set exists and user has access
    const adSet = await AdAudience.findById(adSetId);
    if (!adSet) { res.status(404).json({ error: 'Ad Set not found' }); return; }
    if (!authorizeCompany(req, (adSet as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }

    // Get all ads for this ad set
    const ads = await Ad.find({ adSetId }).sort({ createdAt: -1 });
    res.json(ads);
  } catch (error: any) {
    handleError(res, error);
  }
});

// GET /audiences/by-campaign/:campaignId - Get all ad sets for a specific campaign
router.get('/audiences/by-campaign/:campaignId', async (req: Request, res: Response) => {
  try {
    const { campaignId } = req.params;
    const { AdAudience, AdCampaign } = getModels();

    // First verify the campaign exists and user has access
    const campaign = await AdCampaign.findById(campaignId);
    if (!campaign) { res.status(404).json({ error: 'Campaign not found' }); return; }
    if (!authorizeCompany(req, (campaign as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }

    // Get all ad sets for this campaign
    const audiences = await AdAudience.find({ campaignId }).sort({ createdAt: -1 });
    res.json(audiences);
  } catch (error: any) {
    handleError(res, error);
  }
});

// GET /budgets/by-campaign/:campaignId - Get all budgets for a specific campaign
router.get('/budgets/by-campaign/:campaignId', async (req: Request, res: Response) => {
  try {
    const { campaignId } = req.params;
    const { AdBudget, AdCampaign } = getModels();

    // First verify the campaign exists and user has access
    const campaign = await AdCampaign.findById(campaignId);
    if (!campaign) { res.status(404).json({ error: 'Campaign not found' }); return; }
    if (!authorizeCompany(req, (campaign as any).companyId)) { res.status(403).json({ error: 'Access denied' }); return; }

    // Get all budgets for this campaign
    const budgets = await AdBudget.find({ campaignId }).sort({ createdAt: -1 });
    res.json(budgets);
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// AD CREATIVE IMAGE UPLOAD
// ============================================

/**
 * POST /ads/upload — Upload an ad creative image/video via multipart form data
 * Returns the URL path for the uploaded file
 */
router.post('/upload', requirePermission('ads', 'create'), upload.single('file'), async (req: Request, res: Response) => {
  try {
    if (!req.file) {
      res.status(400).json({ error: 'No file uploaded' });
      return;
    }

    const { companyId } = req.body;
    if (!companyId || !authorizeCompany(req, companyId)) {
      // Clean up the uploaded file if authorization fails
      fs.unlink(req.file.path, () => {});
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    // Return the URL path for the uploaded file
    const url = `/uploads/ad-creatives/${req.file.filename}`;

    res.json({
      success: true,
      url,
      fileName: req.file.originalname,
      fileSize: req.file.size,
      mimeType: req.file.mimetype,
    });
  } catch (error: any) {
    console.error('[Ads] File upload error:', error);
    res.status(500).json({ error: 'Failed to upload file', message: error.message });
  }
});

export default router;