import { sleep } from '@/utils';
import { ApiError } from '@/lib/api/errors';
import type { PageParams, Paginated } from '@/lib/api/types';
import { mockGetBookingForReview } from '@/services/bookings/apis/mockApi';
import { MIN_RATING_FOR_SUPPORT_ALERT } from '../constants';
import type {
  CreateReviewRequest,
  ModerateReviewResult,
  ModerationAction,
  ModerationQueueFilters,
  ModerationQueueItem,
  ModerationStatus,
  MyReviewState,
  NurseReviews,
  ReviewEligibility,
  ReviewListItem,
  ReviewsApi,
  SubmitReviewResult,
} from '../types';

/**
 * In-memory `ReviewsApi` — **the primary implementation this phase** (b14 serves submit + the public list,
 * but review-eligibility and my-review-for-booking are REQ-026 gaps and moderation is admin-only/f15 — see
 * `constants.ts`).
 *
 * It is engineered to demo the whole trust loop end-to-end:
 *  - **Published seed** — a per-nurse published-review list (nurse 1 has 7 so the profile tab paginates;
 *    nurses 5/6 have none → the empty state). The aggregate is recomputed **from the published list**, never
 *    stored, so hiding/publishing a review re-derives the average (the server's job, mirrored here).
 *  - **Eligibility** reads a single booking from the shared **f8 bookings store** (`mockGetBookingForReview`)
 *    — a booking is reviewable only when `completed`/`closed` AND not already reviewed (the 1:1 rule).
 *  - **Submission tracking** — `createReview` records the customer's review as `pending_moderation` (it does
 *    **not** enter any public list), so eligibility flips to `already_reviewed` and `getMyReviewForBooking`
 *    returns the persistent "under review" state.
 *  - **Admin moderation queue** — `listModerationQueue`/`moderateReview` back the admin worklist: a seeded set
 *    of `pending_moderation` rows (incl. low-rating reviews with a linked `lowRatingAlertId`) filtered by
 *    status, transitioned statefully in place. A transition returns a **plausible recomputed** nurse aggregate
 *    (the numbers are server-authoritative — the client renders, never computes them).
 *  - **`__mockPublishSubmittedReview(bookingId)`** — dev-only stand-in that moves a *customer-submitted* review
 *    to `published` and onto the nurse profile so a human can watch it appear (aggregate + count updating on the
 *    next fetch). Distinct from the seeded moderation queue above; never wired into a customer/nurse screen.
 *
 * Booking/nurse ids align with the f8 bookings seeds (nurse 1; completed booking 5005) so a submit from a
 * completed booking deep-links correctly.
 */

const MOCK_LATENCY_MS = 300;

/** A submitted review the mock is tracking (client-side stand-in for the missing my-review read, REQ-026). */
interface SubmittedReview {
  id: number;
  bookingId: number;
  nurseProfileId: number;
  rating: number;
  body: string | null;
  tagCodes: string[];
  status: ModerationStatus;
  createdAt: string;
}

/** ISO instant `days` in the past — seeded review timestamps (rendered Shamsi client-side). */
function isoDaysAgo(days: number): string {
  return new Date(Date.now() - days * 86_400_000).toISOString();
}

let nextReviewId = 9100;

// ── Published reviews per nurse (the public profile tab reads these) ──────────────────────────────────────
const PUBLISHED: Record<number, ReviewListItem[]> = {
  1: [
    { id: 9001, rating: 5, body: 'بسیار دقیق و مهربان بود؛ سر وقت رسید و همه‌چیز را توضیح داد.', tagCodes: ['punctual', 'kind', 'professional'], createdAt: isoDaysAgo(3) },
    { id: 9002, rating: 5, body: 'مراقبت حرفه‌ای و تمیز. خیالمان راحت بود.', tagCodes: ['professional', 'clean'], createdAt: isoDaysAgo(9) },
    { id: 9003, rating: 4, body: 'ارتباط خوبی با بیمار برقرار کرد.', tagCodes: ['communicative', 'kind'], createdAt: isoDaysAgo(14) },
    { id: 9004, rating: 5, body: 'واقعاً منظم و قابل‌اعتماد.', tagCodes: ['punctual', 'professional'], createdAt: isoDaysAgo(21) },
    { id: 9005, rating: 4, body: null, tagCodes: ['clean'], createdAt: isoDaysAgo(28) },
    { id: 9006, rating: 5, body: 'از پرستاری‌اش بسیار راضی بودیم.', tagCodes: ['kind', 'communicative'], createdAt: isoDaysAgo(35) },
    { id: 9007, rating: 3, body: 'خوب بود ولی کمی دیر رسید.', tagCodes: ['professional'], createdAt: isoDaysAgo(44) },
  ],
  2: [
    { id: 9021, rating: 5, body: 'با حوصله و مسلط.', tagCodes: ['professional', 'kind'], createdAt: isoDaysAgo(6) },
    { id: 9022, rating: 4, body: 'مراقبت خوبی داشت.', tagCodes: ['communicative'], createdAt: isoDaysAgo(18) },
  ],
  3: [{ id: 9031, rating: 5, body: 'عالی بود، پیشنهاد می‌کنم.', tagCodes: ['punctual', 'clean', 'kind'], createdAt: isoDaysAgo(11) }],
  4: [
    { id: 9041, rating: 4, body: 'قابل اعتماد و آرام.', tagCodes: ['kind'], createdAt: isoDaysAgo(4) },
    { id: 9042, rating: 5, body: 'خیلی حرفه‌ای برخورد کرد.', tagCodes: ['professional', 'communicative'], createdAt: isoDaysAgo(20) },
  ],
  // nurses 5 & 6: no published reviews → the empty state on their profile tab.
};

const submissions = new Map<number, SubmittedReview>();

// ── Admin moderation queue (the f13 admin worklist reads this) ────────────────────────────────────────────
/**
 * A seeded moderation worklist: mostly `pending_moderation` rows (the default view) with a couple already
 * transitioned so a status filter has something to show. It **includes low-rating reviews** (rating 1/2 with a
 * linked `lowRatingAlertId`) alongside normal ones. Rows are mutated in place by `moderateReview`, so once a
 * row leaves `pending_moderation` it drops out of the pending view (filtered by status) and appears under its
 * new status — statefully, for the session.
 */
const MODERATION_QUEUE: ModerationQueueItem[] = [
  { id: 9301, bookingId: 5011, nurseProfileId: 1, customerProfileId: 7001, rating: 2, body: 'کمی دیر رسید و ارتباط ضعیفی داشت.', tagCodes: ['communicative'], moderationStatus: 'pending_moderation', moderationReason: null, lowRatingAlertId: 4501, createdAt: isoDaysAgo(1) },
  { id: 9302, bookingId: 5012, nurseProfileId: 2, customerProfileId: 7002, rating: 5, body: 'مراقبت عالی و حرفه‌ای؛ کاملاً راضی بودیم.', tagCodes: ['professional', 'kind'], moderationStatus: 'pending_moderation', moderationReason: null, lowRatingAlertId: null, createdAt: isoDaysAgo(1) },
  { id: 9303, bookingId: 5013, nurseProfileId: 1, customerProfileId: 7003, rating: 1, body: 'اصلاً سر وقت نیامد.', tagCodes: [], moderationStatus: 'pending_moderation', moderationReason: null, lowRatingAlertId: 4502, createdAt: isoDaysAgo(2) },
  { id: 9304, bookingId: 5014, nurseProfileId: 3, customerProfileId: 7004, rating: 4, body: 'تمیز و منظم بود.', tagCodes: ['clean', 'punctual'], moderationStatus: 'pending_moderation', moderationReason: null, lowRatingAlertId: null, createdAt: isoDaysAgo(3) },
  { id: 9305, bookingId: 5015, nurseProfileId: 4, customerProfileId: 7005, rating: 5, body: null, tagCodes: ['professional'], moderationStatus: 'published', moderationReason: null, lowRatingAlertId: null, createdAt: isoDaysAgo(6) },
];

/** action → resulting moderation status (the human decision; always overrides the AI pre-screen). */
const ACTION_TO_STATUS: Record<ModerationAction, ModerationStatus> = {
  publish: 'published',
  hide: 'hidden',
  reject: 'rejected',
  unpublish: 'pending_moderation',
};

/** 2-dp average over a nurse's currently-published reviews (server-recomputed; mirrored here). */
function aggregateFor(nurseProfileId: number): { averageRating: number; publishedCount: number } {
  const list = PUBLISHED[nurseProfileId] ?? [];
  if (list.length === 0) return { averageRating: 0, publishedCount: 0 };
  const sum = list.reduce((acc, r) => acc + r.rating, 0);
  return { averageRating: Math.round((sum / list.length) * 100) / 100, publishedCount: list.length };
}

function paginate<T>(all: T[], params: PageParams): Paginated<T> {
  const page = Math.max(1, params.page ?? 1);
  const pageSize = Math.max(1, params.pageSize ?? all.length);
  const start = (page - 1) * pageSize;
  return { items: all.slice(start, start + pageSize), total: all.length, page, pageSize };
}

/** Is this booking (from the shared f8 store) in a review-eligible terminal state? */
function isReviewableStatus(status: string): boolean {
  return status === 'completed' || status === 'closed';
}

/**
 * A **plausible recomputed-looking** nurse aggregate returned after a transition. The real server recomputes
 * `averageRating`/`totalReviews` from source in the same transaction — the numbers are server-authoritative; the
 * mock just returns believable values. A `publish` folds the moderated review's rating into the nurse's
 * published rollup; the other actions leave the published set unchanged here.
 */
function recomputedAggregateFor(
  nurseProfileId: number,
  action: ModerationAction,
  rating: number,
): { averageRating: number; totalReviews: number } {
  const base = aggregateFor(nurseProfileId);
  const totalReviews = base.publishedCount + (action === 'publish' ? 1 : 0);
  if (totalReviews === 0) return { averageRating: 0, totalReviews: 0 };
  const foldedSum = base.averageRating * base.publishedCount + (action === 'publish' ? rating : 0);
  return { averageRating: Math.round((foldedSum / totalReviews) * 100) / 100, totalReviews };
}

export const reviewsMockApi: ReviewsApi = {
  getNurseReviews: async (nurseProfileId: number, params: PageParams): Promise<NurseReviews> => {
    await sleep(MOCK_LATENCY_MS);
    // Newest-first, published only — the mock never returns a submission (it is pending_moderation).
    const list = [...(PUBLISHED[nurseProfileId] ?? [])].sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
    return { aggregate: aggregateFor(nurseProfileId), reviews: paginate(list, params) };
  },

  getReviewEligibility: async (bookingId: number): Promise<ReviewEligibility> => {
    await sleep(MOCK_LATENCY_MS);
    if (submissions.has(bookingId)) return { canReview: false, reason: 'already_reviewed' };
    let booking;
    try {
      booking = mockGetBookingForReview(bookingId);
    } catch {
      return { canReview: false, reason: 'not_found' };
    }
    if (!isReviewableStatus(booking.status)) return { canReview: false, reason: 'not_completed' };
    return { canReview: true };
  },

  getMyReviewForBooking: async (bookingId: number): Promise<MyReviewState> => {
    await sleep(MOCK_LATENCY_MS);
    const sub = submissions.get(bookingId);
    if (!sub) return { status: 'none', rating: null, body: null, tagCodes: [], createdAt: null };
    return { status: sub.status, rating: sub.rating, body: sub.body, tagCodes: sub.tagCodes, createdAt: sub.createdAt };
  },

  createReview: async (bookingId: number, body: CreateReviewRequest): Promise<SubmitReviewResult> => {
    await sleep(MOCK_LATENCY_MS);
    if (!Number.isInteger(body.rating) || body.rating < 1 || body.rating > 5) {
      throw new ApiError(400, 'Rating must be 1–5', 'rating_out_of_range');
    }
    // 1:1 — a second review for the same booking is a 409 (contract).
    if (submissions.has(bookingId)) throw new ApiError(409, 'Booking already reviewed', 'already_reviewed');

    // Reviews are only for completed/closed bookings — read the shared f8 store (404 → not found).
    const booking = mockGetBookingForReview(bookingId);
    if (!isReviewableStatus(booking.status)) {
      throw new ApiError(409, 'Booking is not completed', 'booking_not_completed');
    }

    const id = nextReviewId++;
    const submission: SubmittedReview = {
      id,
      bookingId,
      nurseProfileId: booking.nurseId,
      rating: body.rating,
      body: body.body?.trim() || null,
      tagCodes: body.tagCodes ?? [],
      // The AI pre-screen keeps clean text pending; it is NOT public until an admin publishes it (f15).
      status: 'pending_moderation',
      createdAt: new Date().toISOString(),
    };
    submissions.set(bookingId, submission);

    return {
      id,
      moderationStatus: 'pending_moderation',
      lowRatingAlertRaised: body.rating <= MIN_RATING_FOR_SUPPORT_ALERT,
    };
  },

  listModerationQueue: async (
    filters: ModerationQueueFilters,
    params: PageParams,
  ): Promise<Paginated<ModerationQueueItem>> => {
    await sleep(MOCK_LATENCY_MS);
    const status = filters.status ?? 'pending_moderation';
    const list = MODERATION_QUEUE.filter((q) => q.moderationStatus === status).sort(
      (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt),
    );
    return paginate(list, params);
  },

  moderateReview: async (
    reviewId: number,
    action: ModerationAction,
    reason?: string,
  ): Promise<ModerateReviewResult> => {
    await sleep(MOCK_LATENCY_MS);
    // hide/reject demand a reason (contract) — surface a 400 so the caller's onError keeps the dialog open.
    if ((action === 'hide' || action === 'reject') && !reason?.trim()) {
      throw new ApiError(400, 'A reason is required to hide or reject a review', 'reason_required');
    }
    const item = MODERATION_QUEUE.find((q) => q.id === reviewId);
    if (!item) throw new ApiError(404, 'Review not found', 'review_not_found');

    const nextStatus = ACTION_TO_STATUS[action];
    // Mutate in place: once it leaves pending_moderation it drops out of the pending view (filtered by status).
    item.moderationStatus = nextStatus;
    item.moderationReason = action === 'hide' || action === 'reject' ? reason?.trim() ?? null : null;

    const agg = recomputedAggregateFor(item.nurseProfileId, action, item.rating);
    return { id: reviewId, moderationStatus: nextStatus, averageRating: agg.averageRating, totalReviews: agg.totalReviews };
  },
};

/**
 * DEV-ONLY: publish a submitted review, standing in for the deferred (f15) admin moderation queue. Moves the
 * customer's `pending_moderation` submission to `published` and prepends it to the nurse's public list so a
 * human can watch it appear on the profile (aggregate + count updating on the next fetch). Not wired into any
 * customer/nurse screen — call it from the console (or a later admin surface). No-op if unknown.
 */
export function __mockPublishSubmittedReview(bookingId: number): void {
  const sub = submissions.get(bookingId);
  if (!sub) return;
  sub.status = 'published';
  const list = PUBLISHED[sub.nurseProfileId] ?? (PUBLISHED[sub.nurseProfileId] = []);
  list.unshift({ id: sub.id, rating: sub.rating, body: sub.body, tagCodes: sub.tagCodes, createdAt: sub.createdAt });
}
