import { Router, Request, Response } from 'express';
import { Prisma } from '@prisma/client';
import prisma from '../lib/prisma';

const router = Router();

class ApiError extends Error {
  statusCode: number;

  constructor(statusCode: number, message: string) {
    super(message);
    this.statusCode = statusCode;
  }
}

function param(req: Request, name: string): string {
  const v = req.params[name];
  return Array.isArray(v) ? v[0] : v;
}

function hasOwn<T extends object>(obj: T, key: string): boolean {
  return Object.prototype.hasOwnProperty.call(obj, key);
}

function pickFields<T extends Record<string, any>>(source: Record<string, any>, keys: Array<keyof T>) {
  const data: Partial<T> = {};
  for (const key of keys) {
    const keyName = String(key);
    if (hasOwn(source, keyName)) {
      data[key] = source[keyName];
    }
  }
  return data;
}

function sendRouteError(res: Response, err: any) {
  if (err instanceof ApiError) {
    res.status(err.statusCode).json({ error: err.message });
    return;
  }
  res.status(500).json({ error: err.message });
}

async function assertBucketRelations(
  bucketId: string,
  data: Record<string, any>,
  allowedKeys: Array<'pillarId' | 'formatId' | 'campaignId' | 'ctaId' | 'slotId'>
) {
  const relationChecks = [];

  if (allowedKeys.includes('pillarId') && data.pillarId) {
    relationChecks.push(
      prisma.mktContentPillar.findUnique({
        where: { id: data.pillarId },
        select: { bucketId: true },
      }).then((record) => ({ key: 'pillarId', record }))
    );
  }

  if (allowedKeys.includes('formatId') && data.formatId) {
    relationChecks.push(
      prisma.mktPostFormat.findUnique({
        where: { id: data.formatId },
        select: { bucketId: true },
      }).then((record) => ({ key: 'formatId', record }))
    );
  }

  if (allowedKeys.includes('campaignId') && data.campaignId) {
    relationChecks.push(
      prisma.mktCampaign.findUnique({
        where: { id: data.campaignId },
        select: { bucketId: true },
      }).then((record) => ({ key: 'campaignId', record }))
    );
  }

  if (allowedKeys.includes('ctaId') && data.ctaId) {
    relationChecks.push(
      prisma.mktCta.findUnique({
        where: { id: data.ctaId },
        select: { bucketId: true },
      }).then((record) => ({ key: 'ctaId', record }))
    );
  }

  if (allowedKeys.includes('slotId') && data.slotId) {
    relationChecks.push(
      prisma.mktScheduleSlot.findUnique({
        where: { id: data.slotId },
        select: { bucketId: true },
      }).then((record) => ({ key: 'slotId', record }))
    );
  }

  const relations = await Promise.all(relationChecks);

  for (const relation of relations) {
    if (!relation.record) {
      throw new ApiError(400, `${relation.key} is invalid`);
    }
    if (relation.record.bucketId !== bucketId) {
      throw new ApiError(400, `${relation.key} does not belong to this bucket`);
    }
  }
}

// ═══════════════════════════════════════════════
// BUCKETS
// ═══════════════════════════════════════════════

router.get('/buckets', async (_req: Request, res: Response) => {
  try {
    const buckets = await prisma.mktChannelBucket.findMany({
      where: { enabled: true },
      orderBy: { sortOrder: 'asc' },
      include: {
        _count: {
          select: {
            pillars: true,
            formats: true,
            campaigns: true,
            drafts: true,
            scheduleSlots: true,
            leads: true,
          },
        },
      },
    });
    res.json(buckets);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.get('/buckets/:id', async (req: Request, res: Response) => {
  try {
    const bucket = await prisma.mktChannelBucket.findUnique({
      where: { id: param(req, 'id') },
      include: {
        _count: {
          select: {
            pillars: true,
            formats: true,
            campaigns: true,
            drafts: true,
            scheduleSlots: true,
            engagementRules: true,
            assets: true,
            leads: true,
          },
        },
      },
    });
    if (!bucket) { res.status(404).json({ error: 'Bucket not found' }); return; }
    res.json(bucket);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktChannelBucketUncheckedCreateInput>(req.body, [
      'name',
      'label',
      'description',
      'icon',
      'color',
      'goals',
      'tone',
      'sortOrder',
      'enabled',
    ]) as Prisma.MktChannelBucketUncheckedCreateInput;
    const bucket = await prisma.mktChannelBucket.create({ data });
    res.status(201).json(bucket);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/buckets/:id', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktChannelBucketUncheckedUpdateInput>(req.body, [
      'name',
      'label',
      'description',
      'icon',
      'color',
      'goals',
      'tone',
      'sortOrder',
      'enabled',
    ]) as Prisma.MktChannelBucketUncheckedUpdateInput;
    const bucket = await prisma.mktChannelBucket.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });
    res.json(bucket);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// PILLARS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/pillars', async (req: Request, res: Response) => {
  try {
    const pillars = await prisma.mktContentPillar.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { sortOrder: 'asc' },
    });
    res.json(pillars);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/pillars', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktContentPillarUncheckedCreateInput>(req.body, [
      'name',
      'description',
      'color',
      'examples',
      'sortOrder',
    ]) as Prisma.MktContentPillarUncheckedCreateInput;
    const pillar = await prisma.mktContentPillar.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(pillar);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/pillars/:id', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktContentPillarUncheckedUpdateInput>(req.body, [
      'name',
      'description',
      'color',
      'examples',
      'sortOrder',
    ]) as Prisma.MktContentPillarUncheckedUpdateInput;
    const pillar = await prisma.mktContentPillar.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });
    res.json(pillar);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.delete('/pillars/:id', async (req: Request, res: Response) => {
  try {
    await prisma.mktContentPillar.delete({ where: { id: param(req, 'id') } });
    res.json({ ok: true });
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// FORMATS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/formats', async (req: Request, res: Response) => {
  try {
    const formats = await prisma.mktPostFormat.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { sortOrder: 'asc' },
    });
    res.json(formats);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/formats', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktPostFormatUncheckedCreateInput>(req.body, [
      'name',
      'description',
      'charLimit',
      'templateBody',
      'sortOrder',
    ]) as Prisma.MktPostFormatUncheckedCreateInput;
    const format = await prisma.mktPostFormat.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(format);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// CTAs
// ═══════════════════════════════════════════════

router.get('/buckets/:id/ctas', async (req: Request, res: Response) => {
  try {
    const ctas = await prisma.mktCta.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { sortOrder: 'asc' },
    });
    res.json(ctas);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/ctas', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktCtaUncheckedCreateInput>(req.body, [
      'label',
      'url',
      'intensity',
      'sortOrder',
    ]) as Prisma.MktCtaUncheckedCreateInput;
    const cta = await prisma.mktCta.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(cta);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// ASSETS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/assets', async (req: Request, res: Response) => {
  try {
    const assets = await prisma.mktAsset.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { createdAt: 'desc' },
    });
    res.json(assets);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/assets', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktAssetUncheckedCreateInput>(req.body, [
      'name',
      'type',
      'url',
      'metadata',
    ]) as Prisma.MktAssetUncheckedCreateInput;
    const asset = await prisma.mktAsset.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(asset);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// CAMPAIGNS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/campaigns', async (req: Request, res: Response) => {
  try {
    const campaigns = await prisma.mktCampaign.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { createdAt: 'desc' },
      include: { _count: { select: { drafts: true, scheduleSlots: true } } },
    });
    res.json(campaigns);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/campaigns', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktCampaignUncheckedCreateInput>(req.body, [
      'name',
      'description',
      'status',
      'startDate',
      'endDate',
      'kpis',
    ]) as Prisma.MktCampaignUncheckedCreateInput;
    const campaign = await prisma.mktCampaign.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(campaign);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/campaigns/:id', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktCampaignUncheckedUpdateInput>(req.body, [
      'name',
      'description',
      'status',
      'startDate',
      'endDate',
      'kpis',
    ]) as Prisma.MktCampaignUncheckedUpdateInput;
    const campaign = await prisma.mktCampaign.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });
    res.json(campaign);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// SCHEDULE SLOTS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/schedule', async (req: Request, res: Response) => {
  try {
    const slots = await prisma.mktScheduleSlot.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: [{ dayOfWeek: 'asc' }, { time: 'asc' }],
      include: { pillar: true, format: true, campaign: true, cta: true },
    });
    res.json(slots);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/schedule', async (req: Request, res: Response) => {
  try {
    const bucketId = param(req, 'id');
    const data = pickFields<Prisma.MktScheduleSlotUncheckedCreateInput>(req.body, [
      'dayOfWeek',
      'time',
      'pillarId',
      'formatId',
      'campaignId',
      'ctaId',
      'label',
      'enabled',
    ]) as Prisma.MktScheduleSlotUncheckedCreateInput;
    await assertBucketRelations(bucketId, data, ['pillarId', 'formatId', 'campaignId', 'ctaId']);
    const slot = await prisma.mktScheduleSlot.create({
      data: { ...data, bucketId },
    });
    res.status(201).json(slot);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/schedule/:id', async (req: Request, res: Response) => {
  try {
    const existing = await prisma.mktScheduleSlot.findUnique({
      where: { id: param(req, 'id') },
      select: { id: true, bucketId: true },
    });
    if (!existing) {
      res.status(404).json({ error: 'Schedule slot not found' });
      return;
    }
    const data = pickFields<Prisma.MktScheduleSlotUncheckedUpdateInput>(req.body, [
      'dayOfWeek',
      'time',
      'pillarId',
      'formatId',
      'campaignId',
      'ctaId',
      'label',
      'enabled',
    ]) as Prisma.MktScheduleSlotUncheckedUpdateInput;
    await assertBucketRelations(existing.bucketId, data, ['pillarId', 'formatId', 'campaignId', 'ctaId']);
    const slot = await prisma.mktScheduleSlot.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });
    res.json(slot);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.delete('/schedule/:id', async (req: Request, res: Response) => {
  try {
    await prisma.mktScheduleSlot.delete({ where: { id: param(req, 'id') } });
    res.json({ ok: true });
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// DRAFTS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/drafts', async (req: Request, res: Response) => {
  try {
    const { status, campaignId, pillarId } = req.query;
    const where: any = { bucketId: param(req, 'id') };
    if (status) where.status = status;
    if (campaignId) where.campaignId = campaignId;
    if (pillarId) where.pillarId = pillarId;

    const drafts = await prisma.mktPostDraft.findMany({
      where,
      orderBy: { updatedAt: 'desc' },
      include: { pillar: true, format: true, campaign: true, cta: true, _count: { select: { revisions: true } } },
    });
    res.json(drafts);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/drafts', async (req: Request, res: Response) => {
  try {
    const bucketId = param(req, 'id');
    const data = pickFields<Prisma.MktPostDraftUncheckedCreateInput>(req.body, [
      'title',
      'body',
      'status',
      'pillarId',
      'formatId',
      'campaignId',
      'ctaId',
      'slotId',
      'tags',
      'metadata',
    ]) as Prisma.MktPostDraftUncheckedCreateInput;
    await assertBucketRelations(bucketId, data, ['pillarId', 'formatId', 'campaignId', 'ctaId', 'slotId']);
    const draft = await prisma.mktPostDraft.create({
      data: { ...data, bucketId },
    });
    // Create initial revision
    await prisma.mktRevision.create({
      data: { draftId: draft.id, body: draft.body, version: 1, changedBy: 'user', note: 'Initial draft' },
    });
    res.status(201).json(draft);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/drafts/:id', async (req: Request, res: Response) => {
  try {
    const existing = await prisma.mktPostDraft.findUnique({
      where: { id: param(req, 'id') },
      select: { id: true, bucketId: true, body: true },
    });
    if (!existing) { res.status(404).json({ error: 'Draft not found' }); return; }
    const data = pickFields<Prisma.MktPostDraftUncheckedUpdateInput>(req.body, [
      'title',
      'body',
      'status',
      'pillarId',
      'formatId',
      'campaignId',
      'ctaId',
      'slotId',
      'tags',
      'metadata',
    ]) as Prisma.MktPostDraftUncheckedUpdateInput;
    await assertBucketRelations(existing.bucketId, data, ['pillarId', 'formatId', 'campaignId', 'ctaId', 'slotId']);

    const draft = await prisma.mktPostDraft.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });

    // Auto-create revision if body changed
    const nextBody = typeof data.body === 'string' ? data.body : undefined;
    if (nextBody !== undefined && nextBody !== existing.body) {
      const lastRev = await prisma.mktRevision.findFirst({
        where: { draftId: draft.id },
        orderBy: { version: 'desc' },
      });
      await prisma.mktRevision.create({
        data: {
          draftId: draft.id,
          body: nextBody,
          version: (lastRev?.version || 0) + 1,
          changedBy: req.body.changedBy || 'user',
          note: req.body.revisionNote || null,
        },
      });
    }

    res.json(draft);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.get('/drafts/:id/revisions', async (req: Request, res: Response) => {
  try {
    const revisions = await prisma.mktRevision.findMany({
      where: { draftId: param(req, 'id') },
      orderBy: { version: 'desc' },
    });
    res.json(revisions);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// ENGAGEMENT RULES
// ═══════════════════════════════════════════════

router.get('/buckets/:id/engagement-rules', async (req: Request, res: Response) => {
  try {
    const rules = await prisma.mktEngagementRule.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { sortOrder: 'asc' },
    });
    res.json(rules);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/engagement-rules', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktEngagementRuleUncheckedCreateInput>(req.body, [
      'name',
      'type',
      'config',
      'enabled',
      'sortOrder',
    ]) as Prisma.MktEngagementRuleUncheckedCreateInput;
    const rule = await prisma.mktEngagementRule.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(rule);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/engagement-rules/:id', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktEngagementRuleUncheckedUpdateInput>(req.body, [
      'name',
      'type',
      'config',
      'enabled',
      'sortOrder',
    ]) as Prisma.MktEngagementRuleUncheckedUpdateInput;
    const rule = await prisma.mktEngagementRule.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });
    res.json(rule);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

// ═══════════════════════════════════════════════
// CLIENT LEADS
// ═══════════════════════════════════════════════

router.get('/buckets/:id/leads', async (req: Request, res: Response) => {
  try {
    const leads = await prisma.mktClientLead.findMany({
      where: { bucketId: param(req, 'id') },
      orderBy: { updatedAt: 'desc' },
    });
    res.json(leads);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.post('/buckets/:id/leads', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktClientLeadUncheckedCreateInput>(req.body, [
      'name',
      'company',
      'email',
      'phone',
      'source',
      'stage',
      'value',
      'notes',
      'lastContact',
    ]) as Prisma.MktClientLeadUncheckedCreateInput;
    const lead = await prisma.mktClientLead.create({
      data: { ...data, bucketId: param(req, 'id') },
    });
    res.status(201).json(lead);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

router.patch('/leads/:id', async (req: Request, res: Response) => {
  try {
    const data = pickFields<Prisma.MktClientLeadUncheckedUpdateInput>(req.body, [
      'name',
      'company',
      'email',
      'phone',
      'source',
      'stage',
      'value',
      'notes',
      'lastContact',
    ]) as Prisma.MktClientLeadUncheckedUpdateInput;
    const lead = await prisma.mktClientLead.update({
      where: { id: param(req, 'id') },
      data: { ...data, updatedAt: new Date() },
    });
    res.json(lead);
  } catch (err: any) {
    sendRouteError(res, err);
  }
});

export default router;
