import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import ValidationError from '@/shared/errors/validation';
import {
  assertAuthenticatedTenantUser,
  getCampusId,
  getOrganizationIdOrGlobal,
  requireUserId,
} from '@/services/shared/access';
import {
  USER_PROGRESS_TYPE_VALUES,
  type UserProgressType,
} from '@/shared/constants/user-progress';
import { resolvePagination } from '@/shared/constants/pagination';
import type { UserProgress } from '@/db/models/user_progress';
import type { CurrentUser } from '@/db/api/types';

interface UserProgressInput {
  progress_type: string;
  item_id: string;
  value?: unknown;
  score?: unknown;
  metadata?: unknown;
}

interface UserProgressFilter {
  progress_type?: string;
  item_id?: string;
  limit?: number | string;
  page?: number | string;
}

function assertValidProgressType(progressType: unknown): UserProgressType {
  const match = USER_PROGRESS_TYPE_VALUES.find((item) => item === progressType);
  if (!match) {
    throw new ValidationError();
  }
  return match;
}

function assertValidMutation(data: UserProgressInput): void {
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
    throw new ValidationError();
  }

  assertValidProgressType(data.progress_type);

  if (typeof data.item_id !== 'string' || data.item_id.trim().length === 0) {
    throw new ValidationError();
  }
}

function toDto(record: UserProgress) {
  const plain = record.get({ plain: true });

  return {
    id: plain.id,
    progress_type: plain.progress_type,
    item_id: plain.item_id,
    value: plain.value,
    score: plain.score,
    metadata: plain.metadata,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    userId: plain.userId,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

class UserProgressService {
  static async list(filter: UserProgressFilter, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    const progressType = assertValidProgressType(filter.progress_type);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);

    const organizationId = getOrganizationIdOrGlobal(currentUser);
    const orgFilter = organizationId ? { organizationId } : {};

    const result = await db.user_progress.findAndCountAll({
      where: {
        ...orgFilter,
        userId: requireUserId(currentUser),
        progress_type: progressType,
        ...(filter.item_id ? { item_id: filter.item_id } : {}),
      },
      order: [['createdAt', 'desc']],
      limit,
      offset,
    });

    return {
      rows: result.rows.map(toDto),
      count: result.count,
    };
  }

  static async upsert(data: UserProgressInput, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    assertValidMutation(data);

    const organizationId = getOrganizationIdOrGlobal(currentUser);
    const orgFilter = organizationId ? { organizationId } : {};
    const progressType = assertValidProgressType(data.progress_type);
    const itemId = data.item_id.trim();

    return withTransaction(async (transaction) => {
      const existing = await db.user_progress.findOne({
        where: {
          ...orgFilter,
          userId: requireUserId(currentUser),
          progress_type: progressType,
          item_id: itemId,
        },
        transaction,
      });

      const payload = {
        progress_type: progressType,
        item_id: itemId,
        value: typeof data.value === 'string' ? data.value : null,
        score: typeof data.score === 'number' ? data.score : null,
        metadata: data.metadata ?? null,
        organizationId: organizationId ?? undefined,
        campusId: getCampusId(currentUser) ?? undefined,
        userId: requireUserId(currentUser),
        updatedById: currentUser?.id ?? null,
      };

      if (existing) {
        await existing.update(payload, { transaction });
        return toDto(existing);
      }

      const created = await db.user_progress.create(
        {
          ...payload,
          createdById: requireUserId(currentUser),
        },
        { transaction },
      );

      return toDto(created);
    });
  }

  static async removeByItem(
    filter: UserProgressFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);
    const progressType = assertValidProgressType(filter.progress_type);

    if (
      typeof filter.item_id !== 'string' ||
      filter.item_id.trim().length === 0
    ) {
      throw new ValidationError();
    }

    const organizationId = getOrganizationIdOrGlobal(currentUser);
    const orgFilter = organizationId ? { organizationId } : {};

    const deletedCount = await db.user_progress.destroy({
      where: {
        ...orgFilter,
        userId: requireUserId(currentUser),
        progress_type: progressType,
        item_id: filter.item_id.trim(),
      },
    });

    return { deletedCount };
  }
}

export default UserProgressService;
