import Joi from "joi";

import { AuthUser } from "@/contexts/auth/domain";
import { ListArticlesResult, ListArticlesUseCase } from "@/contexts/catalog/application";
import { Article } from "@/contexts/catalog/domain";
import { QueryCriteriaService } from "@/contexts/common/application/services";
import { IServerError } from "@/contexts/common/domain/errors";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import {
  ICollection,
  IListArticles_Response_DTO,
  IListResponse_DTO,
  IQueryCriteria,
  Language,
  Result,
  RuleValidator,
} from "@shared/contexts";
import { ICatalogContext } from "../../..";
import { IListArticlesPresenter } from "./presenter";

import { Request } from "express";
interface AuthenticatedRequest extends Request {
  user?: AuthUser;
}

export class ListArticlesController extends ExpressController {
  private useCase: ListArticlesUseCase;
  private presenter: IListArticlesPresenter;
  private context: ICatalogContext;

  constructor(
    props: {
      useCase: ListArticlesUseCase;
      presenter: IListArticlesPresenter;
    },
    context: ICatalogContext
  ) {
    super();

    const { useCase, presenter } = props;
    this.useCase = useCase;
    this.presenter = presenter;
    this.context = context;
  }

  protected validateQuery(query): Result<any> {
    const schema = Joi.object({
      page: Joi.number().optional(),
      limit: Joi.number().optional(),
      $sort_by: Joi.string().optional(),
      $filters: Joi.string().optional(),
      q: Joi.string().optional().allow(null).allow(""),
    }).optional();

    return RuleValidator.validate(schema, query);
  }

  async executeImpl() {
    const req = this.req as AuthenticatedRequest;
    const { language = Language.createDefaultCode() } = <AuthUser>req.user;

    const queryOrError = this.validateQuery({
      $filters: `lang_code[eq]${language.toString()}`,
      ...this.req.query,
    });

    if (queryOrError.isFailure) {
      return this.clientError(queryOrError.error.message);
    }

    const queryParams = queryOrError.object;

    try {
      const queryCriteria: IQueryCriteria = QueryCriteriaService.parse(queryParams);
      const result: ListArticlesResult = await this.useCase.execute({
        queryCriteria,
      });

      if (result.isFailure) {
        return this.clientError(result.error.message);
      }

      const articles = <ICollection<Article>>result.object;

      return this.ok<IListResponse_DTO<IListArticles_Response_DTO>>(
        this.presenter.mapArray(articles, this.context, {
          page: queryCriteria.pagination.offset,
          limit: queryCriteria.pagination.limit,
        })
      );
    } catch (e: unknown) {
      return this.fail(e as IServerError);
    }
  }
}
