import { UseCaseError } from "@/contexts/common/application/useCases";
import { IServerError } from "@/contexts/common/domain";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import {
  IUpdateInvoice_DTO,
  IUpdateInvoice_Response_DTO,
  RuleValidator,
  UniqueID,
} from "@shared/contexts";

import { Invoice } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "../../../InvoicingContext";

import { UpdateInvoiceUseCase } from "@/contexts/invoicing/application";
import { IUpdateInvoicePresenter } from "./presenter";

export class UpdateInvoiceController extends ExpressController {
  private useCase: UpdateInvoiceUseCase;
  private presenter: IUpdateInvoicePresenter;
  private context: IInvoicingContext;

  constructor(
    props: {
      useCase: UpdateInvoiceUseCase;
      presenter: IUpdateInvoicePresenter;
    },
    context: IInvoicingContext,
  ) {
    super();

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

  async executeImpl(): Promise<any> {
    const { invoiceId } = this.req.params;
    const request: IUpdateInvoice_DTO = this.req.body;

    if (
      RuleValidator.validate(
        RuleValidator.RULE_NOT_NULL_OR_UNDEFINED,
        invoiceId,
      ).isFailure
    ) {
      return this.invalidInputError("Invoice Id param is required!");
    }

    const idOrError = UniqueID.create(invoiceId);
    if (idOrError.isFailure) {
      return this.invalidInputError("Invalid invoice Id param!");
    }

    try {
      const result = await this.useCase.execute({
        id: idOrError.object,
        data: request,
      });

      if (result.isFailure) {
        const { error } = result;

        switch (error.code) {
          case UseCaseError.NOT_FOUND_ERROR:
            return this.notFoundError("Invoice not found", error);

          case UseCaseError.INVALID_INPUT_DATA:
            return this.invalidInputError(error.message);

          case UseCaseError.UNEXCEPTED_ERROR:
            return this.internalServerError(result.error.message, result.error);

          case UseCaseError.REPOSITORY_ERROR:
            return this.conflictError(result.error, result.error.details);

          default:
            return this.clientError(result.error.message);
        }
      }

      const invoice = <Invoice>result.object;

      return this.ok<IUpdateInvoice_Response_DTO>(
        this.presenter.map(invoice, this.context),
      );
    } catch (e: unknown) {
      return this.fail(e as IServerError);
    }
  }
}
