/**
 * Lead Scoring Service
 * Rule-based and event-driven lead scoring with decay
 */

import {
  leadsAdapter,
  leadScoringRulesAdapter,
  leadScoringEventsAdapter,
} from '@/lib/adapters/leads';
import { logger } from '@/lib/logger';
import { createId } from '@paralleldrive/cuid2';
import type {
  ScoringRuleInput,
  ScoringRuleOutput,
  ScoringCondition,
  ScoreBreakdown,
  ScoringEventOutput,
  LeadGrade,
  LeadScoringEventType,
} from '@/lib/types/lead-crm.types';
import { getLeadGrade, DEFAULT_SCORING_RULES } from '@/lib/types/lead-crm.types';
/** JSON-compatible input value (replaces Prisma.InputJsonValue) */
type InputJsonValue = string | number | boolean | null | InputJsonValue[] | { [key: string]: InputJsonValue };

const log = logger.scope('LeadScoringService');

// ═══════════════════════════════════════════════════════════════════════
// Scoring Rules CRUD
// ═══════════════════════════════════════════════════════════════════════

export async function getRules(userId: string): Promise<ScoringRuleOutput[]> {
  const rules = await leadScoringRulesAdapter.findMany({
    where: { userId },
    orderBy: { createdAt: 'desc' },
  });
  return rules.map(mapRuleToOutput);
}

export async function createRule(userId: string, input: ScoringRuleInput): Promise<ScoringRuleOutput> {
  const rule = await leadScoringRulesAdapter.create({
    data: {
      id: createId(),
      userId,
      name: input.name,
      category: input.category,
      condition: input.condition as unknown as InputJsonValue,
      points: input.points,
      isActive: input.isActive ?? true,
    },
  });
  log.info('Scoring rule created', { ruleId: rule.id, userId });
  return mapRuleToOutput(rule);
}

export async function updateRule(userId: string, ruleId: string, input: Partial<ScoringRuleInput>): Promise<ScoringRuleOutput | null> {
  const existing = await leadScoringRulesAdapter.findFirst({ where: { id: ruleId, userId } });
  if (!existing) return null;

  const rule = await leadScoringRulesAdapter.update({
    where: { id: ruleId },
    data: {
      ...(input.name !== undefined && { name: input.name }),
      ...(input.category !== undefined && { category: input.category }),
      ...(input.condition !== undefined && { condition: input.condition as unknown as InputJsonValue }),
      ...(input.points !== undefined && { points: input.points }),
      ...(input.isActive !== undefined && { isActive: input.isActive }),
    },
  });
  return mapRuleToOutput(rule);
}

export async function deleteRule(userId: string, ruleId: string): Promise<boolean> {
  const existing = await leadScoringRulesAdapter.findFirst({ where: { id: ruleId, userId } });
  if (!existing) return false;
  await leadScoringRulesAdapter.delete({ where: { id: ruleId } });
  return true;
}

// ═══════════════════════════════════════════════════════════════════════
// Score Calculation
// ═══════════════════════════════════════════════════════════════════════

export async function calculateScore(leadId: string): Promise<number> {
  const lead = (await leadsAdapter.findUnique({ where: { id: leadId } })) as any;
  if (!lead) throw new Error('Lead not found');

  const rules = await leadScoringRulesAdapter.findMany({
    where: { userId: lead.userId, isActive: true },
  });

  // Auto-create default rules if none exist
  if (rules.length === 0) {
    await createDefaultScoringRules(lead.userId);
    return calculateScore(leadId);
  }

  let newScore = 0;
  const scoreBefore = lead.score;

  for (const rule of rules) {
    const condition = rule.condition as unknown as ScoringCondition;
    if (evaluateCondition(condition, lead)) {
      newScore += rule.points;
    }
  }

  // Add event-based points
  const events = await leadScoringEventsAdapter.findMany({
    where: { leadId, eventType: { in: ['RULE_MATCH', 'MANUAL_ADJUST'] } },
  });
  const eventPoints = events.reduce((sum: number, e: { points: number }) => sum + e.points, 0);
  newScore += eventPoints;

  // Clamp score 0-100
  newScore = Math.max(0, Math.min(100, newScore));

  // Update lead score
  if (newScore !== scoreBefore) {
    await leadsAdapter.update({
      where: { id: leadId },
      data: { score: newScore, updatedAt: new Date() },
    });

    // Log score change event
    await leadScoringEventsAdapter.create({
      data: {
        id: createId(),
        leadId,
        eventType: 'RULE_MATCH',
        points: newScore - scoreBefore,
        scoreBefore,
        scoreAfter: newScore,
        metadata: { recalculated: true } as InputJsonValue,
      },
    });
  }

  return newScore;
}

export async function processEvent(
  leadId: string,
  eventType: string,
  metadata?: Record<string, unknown>,
): Promise<number> {
  const lead = (await leadsAdapter.findUnique({ where: { id: leadId } })) as any;
  if (!lead) throw new Error('Lead not found');

  // Find matching event-based rules
  const rules = await leadScoringRulesAdapter.findMany({
    where: { userId: lead.userId, isActive: true },
  });

  let pointsEarned = 0;
  for (const rule of rules) {
    const condition = rule.condition as unknown as ScoringCondition;
    if (condition.operator === 'event' && condition.eventType === eventType) {
      pointsEarned += rule.points;
    }
  }

  if (pointsEarned !== 0) {
    const scoreBefore = lead.score;
    const scoreAfter = Math.max(0, Math.min(100, scoreBefore + pointsEarned));

    await leadsAdapter.update({
      where: { id: leadId },
      data: { score: scoreAfter, updatedAt: new Date() },
    });

    await leadScoringEventsAdapter.create({
      data: {
        id: createId(),
        leadId,
        eventType: 'RULE_MATCH',
        ruleName: `Event: ${eventType}`,
        points: pointsEarned,
        scoreBefore,
        scoreAfter,
        metadata: (metadata ?? { eventType }) as InputJsonValue,
      },
    });

    log.info('Score event processed', { leadId, eventType, pointsEarned, scoreAfter });
    return scoreAfter;
  }

  return lead.score;
}

export async function applyDecay(userId: string): Promise<{ processed: number; decayed: number }> {
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const inactiveLeads = (await leadsAdapter.findMany({
    where: {
      userId,
      score: { gt: 0 },
      lastActivityAt: { lt: thirtyDaysAgo },
    },
  })) as any[];

  let decayed = 0;
  for (const lead of inactiveLeads) {
    const scoreBefore = lead.score;
    const scoreAfter = Math.max(0, scoreBefore - 5);

    if (scoreAfter !== scoreBefore) {
      await leadsAdapter.update({
        where: { id: lead.id },
        data: { score: scoreAfter, updatedAt: new Date() },
      });

      await leadScoringEventsAdapter.create({
        data: {
          id: createId(),
          leadId: lead.id,
          eventType: 'DECAY',
          points: -5,
          scoreBefore,
          scoreAfter,
          metadata: { reason: 'Inactive for 30+ days' } as InputJsonValue,
        },
      });

      decayed++;
    }
  }

  log.info('Score decay applied', { userId, processed: inactiveLeads.length, decayed });
  return { processed: inactiveLeads.length, decayed };
}

export async function getScoreBreakdown(leadId: string): Promise<ScoreBreakdown> {
  const lead = (await leadsAdapter.findUnique({ where: { id: leadId } })) as any;
  if (!lead) throw new Error('Lead not found');

  const events = await leadScoringEventsAdapter.findMany({
    where: { leadId },
    orderBy: { createdAt: 'desc' },
    take: 50,
  });

  const ruleMatches = events
    .filter((e) => e.ruleName)
    .map((e) => ({
      ruleName: e.ruleName!,
      points: e.points,
      matchedAt: e.createdAt,
    }));

  return {
    totalScore: lead.score,
    grade: getLeadGrade(lead.score),
    events: events.map(mapEventToOutput),
    ruleMatches,
  };
}

export function assignGrade(score: number): LeadGrade {
  return getLeadGrade(score);
}

// ═══════════════════════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════════════════════

async function createDefaultScoringRules(userId: string): Promise<void> {
  await Promise.all(
    DEFAULT_SCORING_RULES.map((rule) =>
      leadScoringRulesAdapter.create({
        data: {
          id: createId(),
          userId,
          name: rule.name,
          category: rule.category,
          condition: rule.condition as unknown as InputJsonValue,
          points: rule.points,
          isActive: true,
        },
      }),
    ),
  );
  log.info('Default scoring rules created', { userId });
}

function evaluateCondition(condition: ScoringCondition, lead: Record<string, unknown>): boolean {
  if (condition.operator === 'event') return false; // Events are processed separately

  const field = condition.field;
  if (!field) return false;

  const value = lead[field];

  switch (condition.operator) {
    case 'exists':
      return value !== null && value !== undefined && value !== '';
    case 'not_exists':
      return value === null || value === undefined || value === '';
    case 'equals':
      return value === condition.value;
    case 'contains':
      return typeof value === 'string' && typeof condition.value === 'string' && value.toLowerCase().includes(condition.value.toLowerCase());
    case 'gt':
      return typeof value === 'number' && typeof condition.value === 'number' && value > condition.value;
    case 'lt':
      return typeof value === 'number' && typeof condition.value === 'number' && value < condition.value;
    case 'in':
      return Array.isArray(condition.value) && condition.value.includes(String(value));
    default:
      return false;
  }
}

function mapRuleToOutput(rule: Record<string, unknown>): ScoringRuleOutput {
  const r = rule as Record<string, unknown>;
  return {
    id: r.id as string,
    userId: r.userId as string,
    name: r.name as string,
    category: r.category as string,
    condition: r.condition as ScoringCondition,
    points: r.points as number,
    isActive: r.isActive as boolean,
    createdAt: r.createdAt as Date,
    updatedAt: r.updatedAt as Date,
  };
}

function mapEventToOutput(event: Record<string, unknown>): ScoringEventOutput {
  const e = event as Record<string, unknown>;
  return {
    id: e.id as string,
    leadId: e.leadId as string,
    eventType: e.eventType as LeadScoringEventType,
    ruleName: (e.ruleName as string) ?? null,
    points: e.points as number,
    scoreBefore: e.scoreBefore as number,
    scoreAfter: e.scoreAfter as number,
    metadata: (e.metadata as Record<string, unknown>) ?? null,
    createdAt: e.createdAt as Date,
  };
}
