/**
 * Competitor Routes
 */

import express, { Request, Response } from 'express';
import { body, validationResult } from 'express-validator';
import { getModels } from '../models';
import { authenticateJwtOrApiToken } from '../middleware/dualAuth';
import { requirePermission } from '../middleware/permissions';
import { isNegativeNumericValue } from '../utils/numericValidation';
import { escapeRegex } from '../utils/escapeRegex';

const router = express.Router();

/** Duplicate-name message — one wording for create and update. */
const DUPLICATE_COMPETITOR_MESSAGE = 'A competitor with this name already exists';

/**
 * Find an existing competitor of the same company whose name matches `name`
 * case-insensitively (surrounding whitespace ignored), optionally excluding one
 * record so a competitor can be saved without clashing with itself.
 *
 * This is the single source of duplicate detection for the module: repeated AI
 * generations from the same prompt return the same company name, and without this
 * check each run created another copy of it.
 */
async function findDuplicateCompetitor(
  Competitor: any,
  companyId: string,
  name: string,
  excludeId?: string,
): Promise<any | null> {
  const trimmed = String(name ?? '').trim();
  if (!trimmed) return null;

  const matches = await Competitor.find({
    companyId,
    name: { $regex: new RegExp(`^${escapeRegex(trimmed)}$`, 'i') },
  });

  // `excludeId` is filtered here rather than with `_id: { $ne: ... }` so the check
  // behaves identically under the real and the mock model layer.
  const others = (matches || []).filter((c: any) => {
    if (!excludeId) return true;
    return String(c._id ?? c.id) !== String(excludeId);
  });

  return others[0] || null;
}

// Shared negative-value guards for numeric competitor fields stored as free-form strings
const nonNegativeCompetitorValidators = [
  body('fundingRaised')
    .optional({ checkFalsy: true })
    .custom((v) => !isNegativeNumericValue(v))
    .withMessage('Funding raised cannot be negative'),
  body('marketShare')
    .optional({ checkFalsy: true })
    .custom((v) => !isNegativeNumericValue(v))
    .withMessage('Market share cannot be negative'),
];

router.use(authenticateJwtOrApiToken);

// Get all competitors for a company
router.get('/:companyId', async (req: Request, res: Response) => {
  try {
    const { companyId } = req.params;
    const { Competitor } = getModels();

    if (!req.user!.companyIds.includes(companyId) && req.user!.role !== 'admin') {
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    const competitors = await Competitor.find({ companyId });
    res.json(competitors);
  } catch (error) {
    res.status(500).json({ error: 'Failed to get competitors' });
  }
});

// Get single competitor
router.get('/detail/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { Competitor } = getModels();

    const competitor = await Competitor.findById(id);
    if (!competitor) {
      res.status(404).json({ error: 'Competitor not found' });
      return;
    }

    if (!req.user!.companyIds.includes(competitor.companyId) && req.user!.role !== 'admin') {
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    res.json(competitor);
  } catch (error) {
    res.status(500).json({ error: 'Failed to get competitor' });
  }
});

// Create competitor
router.post(
  '/',
  requirePermission('competitors', 'create'),
  [
    body('name').trim().notEmpty().withMessage('Competitor name is required'),
    body('companyId').notEmpty().withMessage('Company ID is required'),
    body('threatLevel')
      .isIn(['low', 'medium', 'high', 'critical'])
      .withMessage('Invalid threat level'),
    ...nonNegativeCompetitorValidators,
  ],
  async (req: Request, res: Response) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        res.status(400).json({ errors: errors.array() });
        return;
      }

      if (!req.user!.companyIds.includes(req.body.companyId) && req.user!.role !== 'admin') {
        res.status(403).json({ error: 'Access denied' });
        return;
      }

      const { Competitor } = getModels();

      // Same company + same name (case-insensitive) → the record already exists.
      const duplicate = await findDuplicateCompetitor(Competitor, req.body.companyId, req.body.name);
      if (duplicate) {
        res.status(409).json({ error: DUPLICATE_COMPETITOR_MESSAGE });
        return;
      }

      const competitor = new Competitor({ ...req.body, name: String(req.body.name).trim() });
      await competitor.save();

      res.status(201).json(competitor);
    } catch (error) {
      res.status(500).json({ error: 'Failed to create competitor' });
    }
  }
);

// Update competitor
router.put(
  '/:id',
  requirePermission('competitors', 'edit'),
  nonNegativeCompetitorValidators,
  async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ errors: errors.array() });
      return;
    }

    const { id } = req.params;
    const { Competitor } = getModels();

    const competitor = await Competitor.findById(id);
    if (!competitor) {
      res.status(404).json({ error: 'Competitor not found' });
      return;
    }

    if (!req.user!.companyIds.includes(competitor.companyId) && req.user!.role !== 'admin') {
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    // Renaming must not collide with another competitor of the same company.
    if (req.body.name !== undefined && String(req.body.name).trim()) {
      const newName = String(req.body.name).trim();
      const duplicate = await findDuplicateCompetitor(Competitor, competitor.companyId, newName, id);
      if (duplicate) {
        res.status(409).json({ error: DUPLICATE_COMPETITOR_MESSAGE });
        return;
      }
      req.body.name = newName;
    }

    Object.assign(competitor, req.body, { updatedAt: new Date().toISOString() });
    await competitor.save();

    res.json(competitor);
  } catch (error) {
    res.status(500).json({ error: 'Failed to update competitor' });
  }
});

// Delete competitor
router.delete('/:id', requirePermission('competitors', 'delete'), async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { Competitor } = getModels();

    const competitor = await Competitor.findById(id);
    if (!competitor) {
      res.status(404).json({ error: 'Competitor not found' });
      return;
    }

    if (!req.user!.companyIds.includes(competitor.companyId) && req.user!.role !== 'admin') {
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    await Competitor.findByIdAndDelete(id);
    res.json({ message: 'Competitor deleted successfully' });
  } catch (error) {
    res.status(500).json({ error: 'Failed to delete competitor' });
  }
});

export default router;
