import {
  IUseCase,
  IUseCaseError,
  IUseCaseRequest,
  UseCaseError,
} from "@/contexts/common/application/useCases";
import { IRepositoryManager } from "@/contexts/common/domain";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import { Collection, IDomainError, Result, UniqueID, UTCDateValue } from "@shared/contexts";
import {
  Dealer,
  IQuoteRepository,
  QuoteCustomerReference,
  QuoteItem,
  QuoteReference,
  QuoteStatus,
} from "../../domain";

import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { SequelizeBusinessTransactionType } from "@/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction";
import { Quote } from "../../domain/entities/Quotes/Quote";
import { ISalesContext } from "../../infrastructure";
import { findQuoteById, generateQuoteReferenceForDealer } from "../services";

export interface IDuplicateQuoteUseCaseRequest extends IUseCaseRequest {
  sourceId: UniqueID;
}

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

export class DuplicateQuoteUseCase
  implements IUseCase<IDuplicateQuoteUseCaseRequest, Promise<DuplicateQuoteResponseOrError>>
{
  private _adapter: ISequelizeAdapter;
  private _repositoryManager: IRepositoryManager;
  private _dealer: Dealer;
  private _transaction: SequelizeBusinessTransactionType;

  constructor(context: ISalesContext) {
    this._adapter = context.adapter;
    this._repositoryManager = context.repositoryManager;
    this._dealer = context.dealer!;
  }

  async execute(request: IDuplicateQuoteUseCaseRequest): Promise<DuplicateQuoteResponseOrError> {
    const { sourceId } = request;
    const targetId = UniqueID.generateNewID().object;

    const transaction = this._adapter.startTransaction();
    const QuoteRepoBuilder = this._repositoryManager.getRepository<IQuoteRepository>("Quote");

    let sourceQuote: Quote | null = null;
    let quoteReference: QuoteReference;
    let targetQuoteOrError: Result<Quote, IDomainError>;

    // Buscar el quote
    try {
      return await transaction.complete(async (t) => {
        const quoteRepo = QuoteRepoBuilder({ transaction: t });

        sourceQuote = await findQuoteById(sourceId, quoteRepo);

        if (!sourceQuote) {
          return Result.fail(UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "Quote not found"));
        }

        // Generate Reference
        quoteReference = await generateQuoteReferenceForDealer(this._dealer, quoteRepo);

        targetQuoteOrError = this._tryDuplicateQuoteInstance(sourceQuote, targetId, quoteReference);

        await quoteRepo.create(targetQuoteOrError.object);
        return Result.ok<Quote>(targetQuoteOrError.object);
      });
    } catch (error: unknown) {
      const _error = error as IInfrastructureError;
      return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
    }
  }

  private _tryDuplicateQuoteInstance(
    sourceQuote: Quote,
    targetId: UniqueID,
    targetReference: QuoteReference
  ): Result<Quote, IDomainError> {
    const dealerId: UniqueID = this._dealer.id;

    const status = QuoteStatus.createDraft();
    const date = UTCDateValue.createCurrentDate().object;
    const customerReference = QuoteCustomerReference.create("").object;
    const dateSent = UTCDateValue.create(null).object;

    // items
    let items: Collection<QuoteItem>;

    try {
      items = new Collection<QuoteItem>(
        sourceQuote.items.toArray().map((item) => QuoteItem.create(item).object)
      );
    } catch (e: unknown) {
      return Result.fail(e as IDomainError);
    }

    return Quote.create(
      {
        status,
        date,
        reference: targetReference,
        language: sourceQuote.language,
        customerReference,
        customer: sourceQuote.customer,
        currency: sourceQuote.currency,
        paymentMethod: sourceQuote.paymentMethod,
        notes: sourceQuote.notes,
        validity: sourceQuote.validity,

        discount: sourceQuote.discount,
        tax: sourceQuote.tax,

        items,

        dealerId,
        dateSent,
      },
      targetId
    );
  }
}
