import {
  IUseCase,
  IUseCaseError,
  IUseCaseRequest,
  UseCaseError,
} from "@/contexts/common/application";
import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import {
  CurrencyData,
  DomainError,
  IDomainError,
  IUpdateDealer_Request_DTO,
  KeyValueMap,
  Language,
  Name,
  Result,
  UniqueID,
} from "@shared/contexts";
import { Dealer, IDealerRepository } from "../../domain";

export interface IUpdateDealerUseCaseRequest extends IUseCaseRequest {
  id: UniqueID;
  dealerDTO: IUpdateDealer_Request_DTO;
}

export type UpdateDealerResponseOrError =
  | Result<never, IUseCaseError> // Misc errors (value objects)
  | Result<Dealer, never>; // Success!

export class UpdateDealerUseCase
  implements IUseCase<IUpdateDealerUseCaseRequest, Promise<UpdateDealerResponseOrError>>
{
  private _adapter: ISequelizeAdapter;
  private _repositoryManager: IRepositoryManager;

  constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
    this._adapter = props.adapter;
    this._repositoryManager = props.repositoryManager;
  }

  async execute(request: IUpdateDealerUseCaseRequest): Promise<UpdateDealerResponseOrError> {
    const { id, dealerDTO } = request;
    const dealerRepository = this._getDealerRepository();

    // Comprobar que existe el dealer
    const idExists = await dealerRepository().exists(id);
    if (!idExists) {
      const message = `Dealer ID not found`;
      return Result.fail(
        UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, {
          path: "id",
        })
      );
    }

    // Crear usuario
    const dealerOrError = this._tryCreateDealerInstance(dealerDTO, id);

    if (dealerOrError.isFailure) {
      const { error: domainError } = dealerOrError;
      let errorCode = "";
      let message = "";

      switch (domainError.code) {
        // Errores manuales
        case DomainError.INVALID_INPUT_DATA:
          errorCode = UseCaseError.INVALID_INPUT_DATA;
          message = "El distribuidor tiene algún dato erróneo.";
          break;

        default:
          errorCode = UseCaseError.UNEXCEPTED_ERROR;
          message = domainError.message;
          break;
      }

      return Result.fail(UseCaseError.create(errorCode, message, domainError));
    }

    return this._updateDealer(dealerOrError.object);
  }

  private async _updateDealer(dealer: Dealer) {
    // Guardar el contacto
    const transaction = this._adapter.startTransaction();
    const dealerRepository = this._getDealerRepository();
    let dealerRepo: IDealerRepository;

    try {
      await transaction.complete(async (t) => {
        dealerRepo = dealerRepository({ transaction: t });
        await dealerRepo.update(dealer);
      });

      return Result.ok<Dealer>(dealer);
    } catch (error: unknown) {
      const _error = error as IInfrastructureError;
      return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
    }
  }

  private _tryCreateDealerInstance(
    dealerDTO: IUpdateDealer_Request_DTO,
    dealerId: UniqueID
  ): Result<Dealer, IDomainError> {
    const nameOrError = Name.create(dealerDTO.name);
    if (nameOrError.isFailure) {
      return Result.fail(nameOrError.error);
    }

    const languageOrError = Language.createFromCode(dealerDTO.lang_code);
    if (languageOrError.isFailure) {
      return Result.fail(languageOrError.error);
    }

    const currencyOrError = CurrencyData.createDefaultCode();
    if (currencyOrError.isFailure) {
      return Result.fail(currencyOrError.error);
    }

    const DealerStatusOrError = CurrencyData.createDefaultCode();
    if (currencyOrError.isFailure) {
      return Result.fail(currencyOrError.error);
    }

    return Dealer.create(
      {
        name: nameOrError.object,
        currency: currencyOrError.object,
        //status:

        //logo: "",
        language: languageOrError.object,

        additionalInfo: KeyValueMap.create([
          ["contact_information", dealerDTO.contact_information],
          ["default_payment_method", dealerDTO.default_payment_method],
          ["default_notes", dealerDTO.default_notes],
          ["default_legal_terms", dealerDTO.default_legal_terms],
          ["default_quote_validity", dealerDTO.default_quote_validity],
        ]).object,
      },
      dealerId
    );
  }

  private _getDealerRepository() {
    return this._repositoryManager.getRepository<IDealerRepository>("Dealer");
  }
}
