/**
 * Threads (Meta Threads) Publications API Routes
 *
 * Threads-specific publication jobs. Mounted at /api/threads.
 * Reads (list/detail) reuse the platform-generic /api/social-publications
 * routes; only Threads-specific writes live here so the other platform code is
 * untouched.
 *
 * - POST   /upload                    → upload image/video binaries (multipart)
 * - POST   /publish                   → create a publication (draft | publish now | scheduled)
 * - POST   /publications/:id/retry    → re-queue a draft/failed/cancelled publication
 * - POST   /publications/:id/cancel   → cancel (nothing is on Threads until published — local)
 * - DELETE /publications/:id          → remove a draft/failed/cancelled record (+ its files)
 *
 * Note: Threads fetches media from a public URL (served from
 * ${PUBLIC_BASE_URL}/uploads/social-media/...). Scheduling is worker-driven.
 *
 * Publishing isolation: a publication can only target an account owned by the
 * requesting admin, and only its creator can mutate it.
 */

import express, { Request, Response } from 'express';
import { authenticate } from '../middleware/auth';
import { requirePermission } from '../middleware/permissions';
import { getModels } from '../models';
import multer from 'multer';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';

const router = express.Router();

// ============================================
// MULTER — image + video uploads (shared uploads/social-media dir)
// ============================================

const SOCIAL_MEDIA_DIR = path.resolve(process.cwd(), 'uploads', 'social-media');
if (!fs.existsSync(SOCIAL_MEDIA_DIR)) {
  fs.mkdirSync(SOCIAL_MEDIA_DIR, { recursive: true });
}

const IMAGE_TYPES = ['image/jpeg', 'image/png'];
const VIDEO_TYPES = ['video/mp4', 'video/quicktime'];
const IMAGE_MAX = 8 * 1024 * 1024; // Threads image cap
const VIDEO_MAX = 1024 * 1024 * 1024; // Threads video cap (~1GB)

const storage = multer.diskStorage({
  destination: (_req, _file, cb) => cb(null, SOCIAL_MEDIA_DIR),
  filename: (_req, file, cb) => {
    const ext = path.extname(file.originalname) || '';
    cb(null, `${uuidv4()}${ext}`);
  },
});

const uploadMedia = multer({
  storage,
  limits: { fileSize: VIDEO_MAX }, // 1GB ceiling (video); images checked below
  fileFilter: (_req, file, cb) => {
    cb(null, IMAGE_TYPES.includes(file.mimetype) || VIDEO_TYPES.includes(file.mimetype));
  },
});

router.use(authenticate);

// ============================================
// HELPERS
// ============================================

const authorizeCompany = (req: Request, companyId: string): boolean => {
  return req.user!.companyIds.includes(companyId) || req.user!.role === 'admin';
};

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 });
};

const VALID_POST_TYPES = ['text', 'photo', 'video', 'carousel'];
const TEXT_MAX = 500;

function isVideoPath(filePath: string): boolean {
  const ext = path.extname(filePath).toLowerCase();
  return ['.mp4', '.mov', '.m4v'].includes(ext);
}
function isImagePath(filePath: string): boolean {
  return ['.jpg', '.jpeg', '.png'].includes(path.extname(filePath).toLowerCase());
}

/**
 * Validate a Threads publication payload. Returns an error string or null.
 */
function validateThreadsPayload(body: any): string | null {
  const postType = String(body.postType || '');
  if (!VALID_POST_TYPES.includes(postType)) {
    return 'Select a valid post type';
  }

  const message = String(body.message || '');
  if (message.length > TEXT_MAX) return `Threads posts are limited to ${TEXT_MAX} characters`;

  const media: string[] = Array.isArray(body.mediaFilePaths) ? body.mediaFilePaths : [];

  if (postType === 'text' && !message.trim()) return 'Enter some text to post';
  if (postType === 'text' && body.linkAttachment) {
    try { new URL(String(body.linkAttachment)); } catch { return 'Enter a valid URL for the link attachment'; }
  }
  if (postType === 'photo') {
    if (media.length !== 1) return 'A photo post needs exactly one image';
    if (!isImagePath(media[0] || '')) return 'The photo post needs an image file (JPEG/PNG)';
  }
  if (postType === 'video') {
    if (media.length !== 1) return 'A video post needs exactly one video file';
    if (!isVideoPath(media[0] || '')) return 'The video post needs a video file';
  }
  if (postType === 'carousel') {
    if (media.length < 2) return 'A carousel needs at least 2 items';
    if (media.length > 20) return 'Threads carousels allow at most 20 items';
    if (!media.every((m) => isImagePath(m) || isVideoPath(m))) return 'Carousel items must be images or videos';
  }

  if (body.publishAt) {
    const publishAt = new Date(body.publishAt);
    if (isNaN(publishAt.getTime())) return 'Invalid schedule date';
    const delta = publishAt.getTime() - Date.now();
    if (delta < 2 * 60 * 1000) return 'Schedule time must be at least 2 minutes in the future';
    if (delta > 365 * 24 * 3600 * 1000) return 'Schedule time cannot be more than a year ahead';
  }

  return null;
}

async function loadOwnedPublication(req: Request, res: Response, id: string): Promise<any | null> {
  const { SocialMediaPublication } = getModels();
  const publication = await (SocialMediaPublication as any).findById(id);

  if (!publication) {
    res.status(404).json({ error: 'Publication not found' });
    return null;
  }
  if (publication.platform !== 'threads') {
    res.status(400).json({ error: 'This publication is not a Threads post' });
    return null;
  }
  if (!authorizeCompany(req, publication.companyId)) {
    res.status(403).json({ error: 'Access denied' });
    return null;
  }
  if (publication.createdBy !== req.user!._id.toString()) {
    res.status(403).json({ error: 'Only the admin who created this publication can manage it' });
    return null;
  }
  return publication;
}

// ============================================
// UPLOAD
// ============================================

router.post('/upload', requirePermission('social-media-os', 'upload'), uploadMedia.array('files', 20), async (req: Request, res: Response) => {
  try {
    const files = (req.files as Express.Multer.File[]) || [];
    if (files.length === 0) {
      res.status(400).json({ error: 'No files received (JPEG/PNG image ≤8MB, MP4/MOV video ≤1GB)' });
      return;
    }

    for (const file of files) {
      if (IMAGE_TYPES.includes(file.mimetype) && file.size > IMAGE_MAX) {
        files.forEach((f) => { try { fs.unlinkSync(f.path); } catch { /* ignore */ } });
        res.status(413).json({ error: 'Threads images are limited to 8MB' });
        return;
      }
    }

    const uploads = files.map((file) => ({
      filePath: `/uploads/social-media/${file.filename}`,
      kind: IMAGE_TYPES.includes(file.mimetype) ? 'image' : 'video',
      mimeType: file.mimetype,
      sizeBytes: file.size,
      originalName: file.originalname,
    }));

    res.json({ uploads });
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// CREATE / PUBLISH
// ============================================

router.post('/publish', requirePermission('social-media-os', 'create'), async (req: Request, res: Response) => {
  try {
    const userId = req.user!._id.toString();
    const companyId = req.body.companyId || req.user!.activeCompanyId || req.user!.companyIds[0];

    if (!companyId || !authorizeCompany(req, companyId)) {
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    const validationError = validateThreadsPayload(req.body);
    if (validationError) {
      res.status(400).json({ error: validationError });
      return;
    }

    // The target must be one of the requesting admin's OWN connections
    const { ThreadsAccount, SocialMediaPublication } = getModels();
    const account = await (ThreadsAccount as any).findOne({
      _id: req.body.accountId,
      companyId,
      userId,
    });

    if (!account) {
      res.status(403).json({ error: 'Threads account not found among your connected accounts. You can only publish through accounts you connected yourself.' });
      return;
    }
    if (account.status !== 'connected') {
      res.status(400).json({ error: 'This Threads account needs to be reconnected before publishing' });
      return;
    }

    const isDraft = req.body.action === 'draft';
    const hasSchedule = !!req.body.publishAt && !isDraft;
    // draft → 'draft'; scheduled → 'scheduled' (worker executes at publishAt); now → 'queued'
    const status = isDraft ? 'draft' : hasSchedule ? 'scheduled' : 'queued';

    const postType = req.body.postType;

    const publication = new (SocialMediaPublication as any)({
      companyId,
      createdBy: userId,
      platform: 'threads',
      accountRef: account._id.toString(),
      threadsUserId: account.threadsUserId,
      channelTitle: account.displayName || account.username, // reuse the display field
      pageName: account.username,
      campaignId: req.body.campaignId || null,
      contentRef: req.body.contentRef || null,
      postType,
      message: req.body.message || '',
      mediaFilePaths: Array.isArray(req.body.mediaFilePaths) ? req.body.mediaFilePaths : [],
      linkAttachment: postType === 'text' ? (req.body.linkAttachment || null) : null,
      replyControl: ['everyone', 'accounts_you_follow', 'mentioned_only'].includes(req.body.replyControl) ? req.body.replyControl : 'everyone',
      isDraft,
      publishAt: hasSchedule ? new Date(req.body.publishAt) : null,
      status,
    });

    await publication.save();

    // Best-effort content-calendar sync (mirrors the other platform routes)
    if (publication.campaignId) {
      try {
        const { SocialMediaCampaign } = getModels();
        const campaign = await (SocialMediaCampaign as any).findOne({ _id: publication.campaignId, companyId });
        if (campaign) {
          const goLive = publication.publishAt ? new Date(publication.publishAt) : new Date();
          const entry = {
            id: `pub_${publication._id}`,
            date: goLive.toISOString().split('T')[0],
            time: goLive.toTimeString().slice(0, 5),
            platform: 'threads',
            contentType: publication.postType,
            title: (publication.message || 'Threads post').slice(0, 80),
            status: 'scheduled',
            publicationId: publication._id.toString(),
          };
          campaign.calendarEntries = [...(campaign.calendarEntries || []), entry];
          await campaign.save();
        }
      } catch (calendarError) {
        console.error('Calendar sync failed for Threads publication', publication._id, calendarError);
      }
    }

    res.status(201).json(publication);
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// RETRY / RE-QUEUE
// ============================================

router.post('/publications/:id/retry', requirePermission('social-media-os', 'edit'), async (req: Request, res: Response) => {
  try {
    const publication = await loadOwnedPublication(req, res, req.params.id);
    if (!publication) return;

    if (!['draft', 'failed', 'cancelled'].includes(publication.status)) {
      res.status(400).json({ error: `A publication in "${publication.status}" state cannot be queued` });
      return;
    }

    publication.status = 'queued';
    publication.attemptCount = 0;
    publication.nextAttemptAt = null;
    publication.workerLockedAt = null;
    publication.lastError = null;
    // Clear stale container ids so a retry rebuilds from scratch.
    publication.threadsContainerId = null;
    publication.threadsChildContainerIds = [];
    publication.threadsMediaId = null;
    await publication.save();

    res.json(publication);
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// CANCEL (nothing exists on Threads until published — local cancel)
// ============================================

router.post('/publications/:id/cancel', requirePermission('social-media-os', 'edit'), async (req: Request, res: Response) => {
  try {
    const publication = await loadOwnedPublication(req, res, req.params.id);
    if (!publication) return;

    if (!['draft', 'queued', 'scheduled'].includes(publication.status)) {
      res.status(400).json({ error: `A publication in "${publication.status}" state cannot be cancelled` });
      return;
    }

    publication.status = 'cancelled';
    publication.workerLockedAt = null;
    await publication.save();

    res.json(publication);
  } catch (error: any) {
    handleError(res, error);
  }
});

// ============================================
// DELETE
// ============================================

router.delete('/publications/:id', requirePermission('social-media-os', 'delete'), async (req: Request, res: Response) => {
  try {
    const publication = await loadOwnedPublication(req, res, req.params.id);
    if (!publication) return;

    if (!['draft', 'failed', 'cancelled'].includes(publication.status)) {
      res.status(400).json({ error: 'Only draft, failed, or cancelled publications can be deleted' });
      return;
    }

    const paths: string[] = publication.mediaFilePaths || [];
    if (paths.length > 0) {
      const { SocialMediaPublication } = getModels();
      for (const filePath of paths) {
        const others = await (SocialMediaPublication as any).countDocuments({
          _id: { $ne: publication._id },
          mediaFilePaths: filePath,
        });
        if (others === 0) {
          const absolute = path.resolve(process.cwd(), filePath.replace(/^\//, ''));
          if (absolute.startsWith(SOCIAL_MEDIA_DIR) && fs.existsSync(absolute)) {
            try { fs.unlinkSync(absolute); } catch { /* ignore */ }
          }
        }
      }
    }

    await publication.deleteOne();
    res.json({ message: 'Publication deleted' });
  } catch (error: any) {
    handleError(res, error);
  }
});

export default router;
