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 { tenantWhere } from '@/db/api/shared/repository';
import { resolvePagination } from '@/shared/constants/pagination';
import type { PolicyAcknowledgments } from '@/db/models/policy_acknowledgments';
import type { CurrentUser } from '@/db/api/types';

interface AcknowledgeInput {
  policyDocumentId?: unknown;
}

interface AcknowledgmentsFilter {
  policyDocumentId?: string;
  limit?: number | string;
  page?: number | string;
}

function requireDocumentId(value: unknown): string {
  if (typeof value !== 'string' || value.trim().length === 0) {
    throw new ValidationError();
  }
  return value.trim();
}

function toDto(record: PolicyAcknowledgments) {
  const plain = record.get({ plain: true });
  return {
    id: plain.id,
    policyDocumentId: plain.policyDocumentId,
    version: plain.version,
    userId: plain.userId,
    acknowledgedAt: plain.acknowledgedAt,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

class PolicyAcknowledgmentsService {
  /** A campus staff member's own acknowledgments (optionally for one document). */
  static async list(
    filter: AcknowledgmentsFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);

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

    const result = await db.policy_acknowledgments.findAndCountAll({
      where: {
        ...orgFilter,
        userId: requireUserId(currentUser),
        ...(filter.policyDocumentId
          ? { policyDocumentId: filter.policyDocumentId }
          : {}),
      },
      order: [['acknowledgedAt', 'desc']],
      limit,
      offset,
    });

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

  /**
   * Record the current user's acknowledgment of a document's **current version**.
   * Idempotent per (user, document, version): re-acknowledging the same version
   * returns the existing row; a bumped document version requires a new one.
   */
  static async acknowledge(data: AcknowledgeInput, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    const policyDocumentId = requireDocumentId(data.policyDocumentId);

    return withTransaction(async (transaction) => {
      // Resolve the document within the caller's tenant; its current version is
      // what gets acknowledged (the client cannot spoof an older version).
      const document = await db.policy_documents.findOne({
        where: { id: policyDocumentId, ...tenantWhere(currentUser) },
        transaction,
      });
      if (!document) {
        throw new ValidationError('policyDocumentNotFound');
      }

      const version = document.version ?? 1;
      const userId = requireUserId(currentUser);

      const existing = await db.policy_acknowledgments.findOne({
        where: { userId, policyDocumentId, version },
        transaction,
      });
      if (existing) {
        return toDto(existing);
      }

      const created = await db.policy_acknowledgments.create(
        {
          policyDocumentId,
          version,
          userId,
          acknowledgedAt: new Date(),
          organizationId: document.organizationId ?? null,
          campusId: getCampusId(currentUser) ?? document.campusId ?? null,
          createdById: userId,
          updatedById: userId,
        },
        { transaction },
      );

      return toDto(created);
    });
  }
}

export default PolicyAcknowledgmentsService;
