import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import { resolvePagination } from '@/shared/constants/pagination';
import ForbiddenError from '@/shared/errors/forbidden';
import ValidationError from '@/shared/errors/validation';
import { hasRoleAccess } from '@/services/shared/access';
import { CONTENT_CATALOG_MANAGER_ROLE_NAMES } from '@/shared/constants/content-catalog';
import type { ContentCatalog } from '@/db/models/content_catalog';
import type { CurrentUser } from '@/db/api/types';

interface ContentCatalogInput {
  content_type?: unknown;
  payload?: unknown;
  active?: boolean;
  importHash?: string | null;
}

function toContentCatalogDto(record: ContentCatalog) {
  const plain = record.get({ plain: true });

  return {
    id: plain.id,
    content_type: plain.content_type,
    payload: plain.payload,
    updatedAt: plain.updatedAt,
  };
}

function assertCanManageContentCatalog(currentUser?: CurrentUser): void {
  if (hasRoleAccess(currentUser, CONTENT_CATALOG_MANAGER_ROLE_NAMES)) {
    return;
  }

  throw new ForbiddenError();
}

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

  return contentType.trim();
}

function assertValidPayload(payload: unknown): unknown {
  if (payload === undefined) {
    throw new ValidationError();
  }

  return payload;
}

class ContentCatalogService {
  static async list(
    filter: { limit?: number | string; page?: number | string } = {},
    currentUser?: CurrentUser,
  ) {
    assertCanManageContentCatalog(currentUser);

    const { limit, offset } = resolvePagination(filter.limit, filter.page);

    const result = await db.content_catalog.findAndCountAll({
      order: [['content_type', 'asc']],
      limit,
      offset,
    });

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

  static async findByType(contentType: unknown) {
    const record = await db.content_catalog.findOne({
      where: {
        content_type: assertValidContentType(contentType),
        active: true,
      },
    });

    if (!record) {
      throw new ValidationError('contentCatalogNotFound');
    }

    return toContentCatalogDto(record);
  }

  static async findManagedByType(contentType: unknown, currentUser?: CurrentUser) {
    assertCanManageContentCatalog(currentUser);

    return this.findByType(contentType);
  }

  static async create(data: ContentCatalogInput, currentUser?: CurrentUser) {
    assertCanManageContentCatalog(currentUser);

    const contentType = assertValidContentType(data?.content_type);
    const payload = assertValidPayload(data?.payload);

    const existingRecord = await db.content_catalog.findOne({
      where: { content_type: contentType },
      paranoid: false,
    });

    if (existingRecord && !existingRecord.deletedAt) {
      throw new ValidationError();
    }

    return withTransaction(async (transaction) => {
      let record: ContentCatalog;
      if (existingRecord) {
        await existingRecord.restore({ transaction });
        record = await existingRecord.update(
          {
            payload,
            active: data.active !== false,
            importHash: data.importHash || null,
          },
          { transaction },
        );
      } else {
        record = await db.content_catalog.create(
          {
            content_type: contentType,
            payload,
            active: data.active !== false,
            importHash: data.importHash || null,
          },
          { transaction },
        );
      }

      return toContentCatalogDto(record);
    });
  }

  static async update(
    contentType: unknown,
    data: ContentCatalogInput,
    currentUser?: CurrentUser,
  ) {
    assertCanManageContentCatalog(currentUser);

    const normalizedContentType = assertValidContentType(contentType);
    const payload = assertValidPayload(data?.payload);

    return withTransaction(async (transaction) => {
      const record = await db.content_catalog.findOne({
        where: { content_type: normalizedContentType },
        transaction,
      });

      if (!record) {
        throw new ValidationError('contentCatalogNotFound');
      }

      await record.update(
        {
          payload,
          active: data.active !== false,
        },
        { transaction },
      );

      return toContentCatalogDto(record);
    });
  }

  static async delete(contentType: unknown, currentUser?: CurrentUser) {
    assertCanManageContentCatalog(currentUser);

    const normalizedContentType = assertValidContentType(contentType);

    return withTransaction(async (transaction) => {
      const record = await db.content_catalog.findOne({
        where: { content_type: normalizedContentType },
        transaction,
      });

      if (!record) {
        throw new ValidationError('contentCatalogNotFound');
      }

      await record.update({ active: false }, { transaction });
      await record.destroy({ transaction });
      return true;
    });
  }
}

export default ContentCatalogService;
