import { Result, ok, err } from '../../../../shared/core/Result';
import { Card } from '../Card';
import { CuratorId } from '../value-objects/CuratorId';
import { ICardPublisher } from '../../application/ports/ICardPublisher';
import { ICardRepository } from '../ICardRepository';
import { ICollectionRepository } from '../ICollectionRepository';
import { AppError } from '../../../../shared/core/AppError';
import { DomainService } from '../../../../shared/domain/DomainService';
import { CardCollectionService } from './CardCollectionService';
import { PublishedRecordId } from '../value-objects/PublishedRecordId';

export class CardLibraryValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'CardLibraryValidationError';
  }
}

export class CardLibraryService implements DomainService {
  constructor(
    private cardRepository: ICardRepository,
    private cardPublisher: ICardPublisher,
    private collectionRepository: ICollectionRepository,
    private cardCollectionService: CardCollectionService,
  ) {}

  async addCardToLibrary(
    card: Card,
    curatorId: CuratorId,
  ): Promise<
    Result<Card, CardLibraryValidationError | AppError.UnexpectedError>
  > {
    try {
      // Check if card is already in curator's library
      const isInLibrary = card.isInLibrary(curatorId);

      if (isInLibrary) {
        // Card is already in library, nothing to do
        return ok(card);
      }
      const addToLibResult = card.addToLibrary(curatorId);
      if (addToLibResult.isErr()) {
        return err(
          new CardLibraryValidationError(
            `Failed to add card to library: ${addToLibResult.error.message}`,
          ),
        );
      }
      let parentCardPublishedRecordId: PublishedRecordId | undefined =
        undefined;

      if (card.parentCardId) {
        // Ensure parent card is in the curator's library
        const parentCardResult = await this.cardRepository.findById(
          card.parentCardId,
        );
        if (parentCardResult.isErr()) {
          return err(
            new CardLibraryValidationError(
              `Failed to fetch parent card: ${parentCardResult.error.message}`,
            ),
          );
        }
        const parentCardValue = parentCardResult.value;

        if (!parentCardValue) {
          return err(new CardLibraryValidationError(`Parent card not found`));
        }
        parentCardPublishedRecordId = parentCardValue.publishedRecordId;
      }

      // Publish card to library
      const publishResult = await this.cardPublisher.publishCardToLibrary(
        card,
        curatorId,
        parentCardPublishedRecordId,
      );
      if (publishResult.isErr()) {
        return err(
          new CardLibraryValidationError(
            `Failed to publish card to library: ${publishResult.error.message}`,
          ),
        );
      }

      // Mark card as published in library
      const markCardAsPublishedResult = card.markCardInLibraryAsPublished(
        curatorId,
        publishResult.value,
      );
      if (markCardAsPublishedResult.isErr()) {
        return err(
          new CardLibraryValidationError(
            `Failed to mark card as published in library: ${markCardAsPublishedResult.error.message}`,
          ),
        );
      }

      // Save updated card
      const saveResult = await this.cardRepository.save(card);
      if (saveResult.isErr()) {
        return err(AppError.UnexpectedError.create(saveResult.error));
      }

      return ok(card);
    } catch (error) {
      return err(AppError.UnexpectedError.create(error));
    }
  }

  async removeCardFromLibrary(
    card: Card,
    curatorId: CuratorId,
  ): Promise<
    Result<Card, CardLibraryValidationError | AppError.UnexpectedError>
  > {
    try {
      // Check if card is in curator's library
      const isInLibrary = card.isInLibrary(curatorId);

      if (!isInLibrary) {
        // Card is not in library, nothing to do
        return ok(card);
      }

      // Get all collections owned by the curator that contain this card
      const collectionsResult =
        await this.collectionRepository.findByCuratorIdContainingCard(
          curatorId,
          card.cardId,
        );
      if (collectionsResult.isErr()) {
        return err(AppError.UnexpectedError.create(collectionsResult.error));
      }

      // Remove card from all curator's collections
      const collections = collectionsResult.value;
      const collectionIds = collections.map(
        (collection) => collection.collectionId,
      );

      if (collectionIds.length > 0) {
        const removeFromCollectionsResult =
          await this.cardCollectionService.removeCardFromCollections(
            card,
            collectionIds,
            curatorId,
          );
        if (removeFromCollectionsResult.isErr()) {
          return err(
            new CardLibraryValidationError(
              `Failed to remove card from collections: ${removeFromCollectionsResult.error.message}`,
            ),
          );
        }
      }

      // Get library info to check if it was published
      const libraryInfo = card.getLibraryInfo(curatorId);
      if (libraryInfo?.publishedRecordId) {
        // Unpublish card from library
        const unpublishResult =
          await this.cardPublisher.unpublishCardFromLibrary(
            libraryInfo.publishedRecordId,
            libraryInfo.curatorId,
          );
        if (unpublishResult.isErr()) {
          return err(
            new CardLibraryValidationError(
              `Failed to unpublish card from library: ${unpublishResult.error.message}`,
            ),
          );
        }
      }

      // Remove card from library
      const removeResult = card.removeFromLibrary(curatorId);
      if (removeResult.isErr()) {
        return err(
          new CardLibraryValidationError(
            `Failed to remove card from library: ${removeResult.error.message}`,
          ),
        );
      }

      // Save updated card
      const saveResult = await this.cardRepository.save(card);
      if (saveResult.isErr()) {
        return err(AppError.UnexpectedError.create(saveResult.error));
      }

      return ok(card);
    } catch (error) {
      return err(AppError.UnexpectedError.create(error));
    }
  }
}
