import type pg from 'pg';
import { createUserNotification } from '../notifications/service.js';

type JsonMap = Record<string, unknown>;

export type BadgeTrigger =
  | 'check_in'
  | 'score'
  | 'feedback_resolved'
  | 'activity_reward'
  | 'practice_count'
  | 'vocabulary_mastered'
  | 'mock_exam_score';

export interface BadgeMetricEvidence {
  checkInStreak?: number;
  lastCheckInPoints?: number;
  score?: number;
  feedbackResolvedCount?: number;
  rewardPoints?: number;
  practiceCount?: number;
  practiceReportCount?: number;
  vocabularyMasteredCount?: number;
  masteredWordsCount?: number;
  mockExamScore?: number;
  mockExamTotalScore?: number;
  mockExamAccuracy?: number;
  mockExamBestScore?: number;
  [key: string]: unknown;
}

interface AutoBadgeRow {
  id: string;
  legacyId: string | null;
  name: string;
  description: string | null;
  category: string | null;
  iconUrl: string | null;
  level: number | null;
  unlockType: string | null;
  conditionField: string | null;
  conditionOperator: string | null;
  conditionValue: string | number | null;
  conditionExtra: JsonMap;
  metadata: JsonMap;
}

export interface AutoBadgeGrant {
  id: string;
  legacyId: string | null;
  userId: string;
  badgeId: string;
  note: string | null;
  metadata: JsonMap;
  grantedAt: string | null;
  createdAt: string;
  updatedAt: string;
  badge: {
    id: string;
    legacyId: string | null;
    name: string;
    description: string | null;
    category: string | null;
    iconUrl: string | null;
    level: number | null;
    unlockType: string | null;
  };
}

const TRIGGER_UNLOCK_TYPES: Record<BadgeTrigger, string[]> = {
  check_in: ['check_in'],
  score: ['score'],
  feedback_resolved: ['feedback_resolved'],
  activity_reward: ['activity_reward'],
  practice_count: ['practice_count'],
  vocabulary_mastered: ['vocabulary_mastered'],
  mock_exam_score: ['mock_exam_score'],
};

const FIELD_ALIASES: Record<string, string[]> = {
  check_in: ['checkInStreak', 'check_in.streak', 'checkIn.streak', 'profile.checkInStreak', 'stats.checkInStreak'],
  last_check_in_points: ['lastCheckInPoints', 'check_in.points', 'checkIn.points', 'stats.lastCheckInPoints'],
  score: ['score', 'user.score', 'profile.score'],
  feedback_resolved: ['feedbackResolvedCount', 'feedback.resolvedCount', 'reports.resolvedCount'],
  reward_points: ['rewardPoints', 'feedback.rewardPoints'],
  activity_reward: ['activityRewardCount', 'activity.rewardCount', 'tasks.claimCount'],
  practice_count: ['practiceCount', 'practice.count', 'practice.reportCount', 'practiceReportCount', 'reports.practiceCount'],
  vocabulary_mastered: ['vocabularyMasteredCount', 'masteredWordsCount', 'vocabulary.masteredCount', 'words.masteredCount'],
  mock_exam_score: ['mockExamScore', 'mockExam.score', 'report.score', 'score'],
  mock_exam_total_score: ['mockExamTotalScore', 'mockExam.totalScore', 'report.totalScore'],
  mock_exam_accuracy: ['mockExamAccuracy', 'mockExam.accuracy', 'report.accuracy'],
  mock_exam_best_score: ['mockExamBestScore', 'mockExam.bestScore', 'reports.mockExamBestScore'],
};

function numberValue(value: unknown) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function textValue(value: unknown) {
  return typeof value === 'string' && value.trim() ? value.trim() : null;
}

function objectValue(value: unknown): JsonMap {
  return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonMap : {};
}

function metricValueForField(field: string | null, evidence: BadgeMetricEvidence, trigger: BadgeTrigger) {
  const normalized = textValue(field);
  if (!normalized) {
    if (trigger === 'check_in') return numberValue(evidence.checkInStreak);
    if (trigger === 'score') return numberValue(evidence.score);
    if (trigger === 'feedback_resolved') return numberValue(evidence.feedbackResolvedCount);
    if (trigger === 'activity_reward') return numberValue(evidence.activityRewardCount);
    if (trigger === 'practice_count') return numberValue(evidence.practiceCount ?? evidence.practiceReportCount);
    if (trigger === 'vocabulary_mastered') return numberValue(evidence.vocabularyMasteredCount ?? evidence.masteredWordsCount);
    if (trigger === 'mock_exam_score') return numberValue(evidence.mockExamScore);
    return null;
  }

  for (const [canonical, aliases] of Object.entries(FIELD_ALIASES)) {
    if (canonical === normalized || aliases.includes(normalized)) {
      if (canonical === 'check_in') return numberValue(evidence.checkInStreak);
      if (canonical === 'last_check_in_points') return numberValue(evidence.lastCheckInPoints);
      if (canonical === 'score') return numberValue(evidence.score);
      if (canonical === 'feedback_resolved') return numberValue(evidence.feedbackResolvedCount);
      if (canonical === 'reward_points') return numberValue(evidence.rewardPoints);
      if (canonical === 'activity_reward') return numberValue(evidence.activityRewardCount);
      if (canonical === 'practice_count') return numberValue(evidence.practiceCount ?? evidence.practiceReportCount);
      if (canonical === 'vocabulary_mastered') return numberValue(evidence.vocabularyMasteredCount ?? evidence.masteredWordsCount);
      if (canonical === 'mock_exam_score') return numberValue(evidence.mockExamScore);
      if (canonical === 'mock_exam_total_score') return numberValue(evidence.mockExamTotalScore);
      if (canonical === 'mock_exam_accuracy') return numberValue(evidence.mockExamAccuracy);
      if (canonical === 'mock_exam_best_score') return numberValue(evidence.mockExamBestScore);
    }
  }

  const direct = evidence[normalized];
  return numberValue(direct);
}

function compareMetric(current: number, target: number, operator: string | null) {
  switch (operator || 'gte') {
    case 'gte':
      return current >= target;
    case 'gt':
      return current > target;
    case 'lte':
      return current <= target;
    case 'lt':
      return current < target;
    case 'eq':
      return current === target;
    default:
      return false;
  }
}

function triggerMatches(row: AutoBadgeRow, trigger: BadgeTrigger) {
  const unlockType = row.unlockType || 'manual';
  if (TRIGGER_UNLOCK_TYPES[trigger].includes(unlockType)) return true;
  if (unlockType !== 'auto') return false;

  const extra = objectValue(row.conditionExtra);
  const triggers = Array.isArray(extra.triggers)
    ? extra.triggers.filter(item => typeof item === 'string')
    : [];
  const triggerName = textValue(extra.trigger);
  return triggerName === trigger || triggers.includes(trigger);
}

function ruleSatisfied(row: AutoBadgeRow, trigger: BadgeTrigger, evidence: BadgeMetricEvidence) {
  const target = numberValue(row.conditionValue);
  if (target === null) return false;
  const current = metricValueForField(row.conditionField, evidence, trigger);
  if (current === null) return false;
  return compareMetric(current, target, row.conditionOperator);
}

export async function autoGrantBadges(
  client: pg.PoolClient,
  input: {
    tenantId: string;
    userId: string;
    trigger: BadgeTrigger;
    evidence: BadgeMetricEvidence;
  },
): Promise<AutoBadgeGrant[]> {
  const badges = await client.query<AutoBadgeRow>(
    `
    select id, legacy_id as "legacyId", name, description, category,
           icon_url as "iconUrl", level, unlock_type as "unlockType",
           condition_field as "conditionField", condition_operator as "conditionOperator",
           condition_value as "conditionValue", condition_extra as "conditionExtra",
           metadata
    from public.badges
    where tenant_id = $1
      and is_active = true
      and coalesce(unlock_type, 'manual') <> 'manual'
    order by sort_order asc, level asc nulls last, created_at asc
    `,
    [input.tenantId],
  );

  const grants: AutoBadgeGrant[] = [];
  for (const badge of badges.rows) {
    if (!triggerMatches(badge, input.trigger)) continue;
    if (!ruleSatisfied(badge, input.trigger, input.evidence)) continue;

    const currentValue = metricValueForField(badge.conditionField, input.evidence, input.trigger);
    const metadata = {
      source: 'auto',
      trigger: input.trigger,
      rule: {
        field: badge.conditionField,
        operator: badge.conditionOperator || 'gte',
        value: numberValue(badge.conditionValue),
      },
      evidence: {
        ...input.evidence,
        matchedValue: currentValue,
      },
    };

    const result = await client.query<AutoBadgeGrant>(
      `
      insert into public.user_badges (
        tenant_id, user_id, badge_id, granted_by, legacy_id,
        note, metadata, granted_at
      )
      values ($1, $2, $3, null, $4, $5, $6::jsonb, now())
      on conflict (tenant_id, user_id, badge_id) do nothing
      returning id, legacy_id as "legacyId", user_id as "userId",
                badge_id as "badgeId", note, metadata,
                granted_at as "grantedAt", created_at as "createdAt",
                updated_at as "updatedAt"
      `,
      [
        input.tenantId,
        input.userId,
        badge.id,
        `auto:${badge.id}:user:${input.userId}`,
        `自动发放：${badge.name}`,
        JSON.stringify(metadata),
      ],
    );

    if (!result.rows[0]) continue;
    await createUserNotification(client, {
      tenantId: input.tenantId,
      userId: input.userId,
      notificationType: 'badge_granted',
      severity: 'success',
      title: `获得勋章：${badge.name}`,
      message: badge.description || '你已解锁新的学习勋章。',
      actionLabel: '查看勋章',
      actionPath: '/student/profile?tab=badges',
      sourceType: 'user_badges',
      sourceId: result.rows[0].id,
      dedupeKey: `badge:${badge.id}:user:${input.userId}`,
      metadata: {
        source: 'auto_badge_grant',
        trigger: input.trigger,
        badgeId: badge.id,
        badgeName: badge.name,
        badgeCategory: badge.category,
        badgeLevel: badge.level,
        evidence: input.evidence,
      },
    });
    grants.push({
      ...result.rows[0],
      badge: {
        id: badge.id,
        legacyId: badge.legacyId,
        name: badge.name,
        description: badge.description,
        category: badge.category,
        iconUrl: badge.iconUrl,
        level: badge.level,
        unlockType: badge.unlockType,
      },
    });
  }

  return grants;
}
