import UIKit
import Shared
import Combine
import XXModels
import Voxophone
import AVFoundation

struct CellFactory {
    var canBuild: (Message) -> Bool

    var build: (Message, UICollectionView, IndexPath) -> UICollectionViewCell

    func callAsFunction(
        item: Message,
        collectionView: UICollectionView,
        indexPath: IndexPath
    ) -> UICollectionViewCell {
        build(item, collectionView, indexPath)
    }
}

extension CellFactory {
    static func combined(factories: [CellFactory]) -> Self {
        .init(
            canBuild: { _ in true },
            build: { item, collectionView, indexPath in
                guard let factory = factories.first(where: { $0.canBuild(item)}) else {
                    fatalError("Couldn't find a factory for \(item). Did you forget to implement?")
                }

                return factory(
                    item: item,
                    collectionView: collectionView,
                    indexPath: indexPath
                )
            }
        )
    }
}

extension CellFactory {
    static func incomingAudio(
        voxophone: Voxophone,
        transfer: @escaping (Data) -> FileTransfer
    ) -> Self {
        .init(
            canBuild: { item in
                guard (item.status == .received || item.status == .receiving),
                      item.replyMessageId == nil,
                      item.fileTransferId != nil else { return false }

                return transfer(item.fileTransferId!).type == "m4a"

            }, build: { item, collectionView, indexPath in
                let ft = transfer(item.fileTransferId!)
                let cell: IncomingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
                let url = FileManager.url(for: "\(ft.name).\(ft.type)")!

                var model = AudioMessageCellState(
                    date: item.date,
                    audioURL: url,
                    isPlaying: false,
                    transferProgress: ft.progress,
                    isLoudspeaker: false,
                    duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
                    playbackTime: 0.0
                )

                cell.leftView.setup(with: model)
                cell.canReply = false
                cell.performReply = {}

                Bubbler.build(audioBubble: cell.leftView, with: item)

                voxophone.$state
                    .sink {
                        switch $0 {
                        case .playing(url, _, time: let time, _):
                            model.isPlaying = true
                            model.playbackTime = time
                        default:
                            model.isPlaying = false
                            model.playbackTime = 0.0
                        }

                        model.isLoudspeaker = $0.isLoudspeaker

                        cell.leftView.setup(with: model)
                    }.store(in: &cell.leftView.cancellables)

                cell.leftView.didTapRight = {
                    guard item.status != .receiving else { return }

                    voxophone.toggleLoudspeaker()
                }

                cell.leftView.didTapLeft = {
                    guard item.status != .receiving else { return }

                    if case .playing(url, _, _, _) = voxophone.state {
                        voxophone.reset()
                    } else {
                        voxophone.load(url)
                        voxophone.play()
                    }
                }

                return cell
            }
        )
    }

    static func outgoingAudio(
        voxophone: Voxophone,
        transfer: @escaping (Data) -> FileTransfer
    ) -> Self {
        .init(
            canBuild: { item in
                guard (item.status == .sent ||
                       item.status == .sending ||
                       item.status == .sendingFailed ||
                       item.status == .sendingTimedOut)
                        && item.replyMessageId == nil
                        && item.fileTransferId != nil else {
                    return false
                }

                return transfer(item.fileTransferId!).type == "m4a"

            }, build: { item, collectionView, indexPath in
                let ft = transfer(item.fileTransferId!)
                let cell: OutgoingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
                let url = FileManager.url(for: "\(ft.name).\(ft.type)")!
                var model = AudioMessageCellState(
                    date: item.date,
                    audioURL: url,
                    isPlaying: false,
                    transferProgress: ft.progress,
                    isLoudspeaker: false,
                    duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
                    playbackTime: 0.0
                )

                cell.rightView.setup(with: model)
                cell.canReply = false
                cell.performReply = {}

                Bubbler.build(audioBubble: cell.rightView, with: item)

                voxophone.$state
                    .sink {
                        switch $0 {
                        case .playing(url, _, time: let time, _):
                            model.isPlaying = true
                            model.playbackTime = time
                        default:
                            model.isPlaying = false
                            model.playbackTime = 0.0
                        }

                        model.isLoudspeaker = $0.isLoudspeaker

                        cell.rightView.setup(with: model)
                    }.store(in: &cell.rightView.cancellables)

                cell.rightView.didTapRight = {
                    voxophone.toggleLoudspeaker()
                }

                cell.rightView.didTapLeft = {
                    if case .playing(url, _, _, _) = voxophone.state {
                        voxophone.reset()
                    } else {
                        voxophone.load(url)
                        voxophone.play()
                    }
                }

                return cell
            }
        )
    }
}

extension CellFactory {
    static func outgoingImage(
        transfer:  @escaping (Data) -> FileTransfer
    ) -> Self {
        .init(
            canBuild: { item in
                guard (item.status == .sent ||
                       item.status == .sending ||
                       item.status == .sendingFailed ||
                       item.status == .sendingTimedOut)
                        && item.replyMessageId == nil
                        && item.fileTransferId != nil else {
                    return false
                }

                return transfer(item.fileTransferId!).type == "jpeg"

            }, build: { item, collectionView, indexPath in
                let ft = transfer(item.fileTransferId!)
                let cell: OutgoingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.build(imageBubble: cell.rightView, with: item, with: transfer(item.fileTransferId!))
                cell.canReply = false
                cell.performReply = {}

                if let image = UIImage(data: ft.data!) {
                    cell.rightView.imageView.image = UIImage(cgImage: image.cgImage!, scale: image.scale, orientation: .up)
                }

                return cell
            }
        )
    }

    static func incomingImage(
        transfer: @escaping (Data) -> FileTransfer
    ) -> Self {
        .init(
            canBuild: { item in
                guard (item.status == .received || item.status == .receiving)
                        && item.replyMessageId == nil
                        && item.fileTransferId != nil else {
                    return false
                }

                return transfer(item.fileTransferId!).type == "jpeg"

            }, build: { item, collectionView, indexPath in
                let ft = transfer(item.fileTransferId!)
                let cell: IncomingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.build(imageBubble: cell.leftView, with: item, with: ft)
                cell.canReply = false
                cell.performReply = {}

                if let data = ft.data {
                    cell.leftView.imageView.image = UIImage(data: data)
                } else {
                    cell.leftView.imageView.image = Asset.transferImagePlaceholder.image
                }

                return cell
            }
        )
    }
}

extension CellFactory {
    static func outgoingReply(
        performReply: @escaping () -> Void,
        replyContent: @escaping (Data) -> (String, String),
        showRound: @escaping (String?) -> Void
    ) -> Self {
        .init(
            canBuild: { item in
                (item.status == .sent || item.status == .sending)
                && item.replyMessageId != nil

            }, build: { item, collectionView, indexPath in
                let cell: OutgoingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.buildReply(
                    bubble: cell.rightView,
                    with: item,
                    reply: replyContent(item.replyMessageId!)
                )

                cell.canReply = item.status == .sent
                cell.performReply = performReply
                cell.rightView.didTapShowRound = { showRound(item.roundURL) }
                return cell
            }
        )
    }

    static func incomingReply(
        performReply: @escaping () -> Void,
        replyContent: @escaping (Data) -> (String, String),
        showRound: @escaping (String?) -> Void
    ) -> Self {
        .init(
            canBuild: { item in
                item.status == .received
                && item.replyMessageId != nil

            }, build: { item, collectionView, indexPath in
                let cell: IncomingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.buildReply(
                    bubble: cell.leftView,
                    with: item,
                    reply: replyContent(item.replyMessageId!)
                )
                cell.canReply = item.status == .received
                cell.performReply = performReply
                cell.leftView.didTapShowRound = { showRound(item.roundURL) }
                cell.leftView.revertBottomStackOrder()
                return cell
            }
        )
    }

    static func outgoingFailedReply(
        performReply: @escaping () -> Void,
        replyContent: @escaping (Data) -> (String, String)
    ) -> Self {
        .init(
            canBuild: { item in
                (item.status == .sendingFailed || item.status == .sendingTimedOut)
                && item.replyMessageId != nil

            }, build: { item, collectionView, indexPath in
                let cell: OutgoingFailedReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.buildReply(
                    bubble: cell.rightView,
                    with: item,
                    reply: replyContent(item.replyMessageId!)
                )

                cell.canReply = false
                cell.performReply = performReply
                return cell
            }
        )
    }
}

extension CellFactory {
    static func incomingText(
        performReply: @escaping () -> Void,
        showRound: @escaping (String?) -> Void
    ) -> Self {
        .init(
            canBuild: { item in
                item.status == .received
                && item.replyMessageId == nil

            }, build: { item, collectionView, indexPath in
                let cell: IncomingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.build(bubble: cell.leftView, with: item)
                cell.canReply = item.status == .received
                cell.performReply = performReply
                cell.leftView.didTapShowRound = { showRound(item.roundURL) }
                cell.leftView.revertBottomStackOrder()
                return cell
            }
        )
    }

    static func outgoingText(
        performReply: @escaping () -> Void,
        showRound: @escaping (String?) -> Void
    ) -> Self {
        .init(
            canBuild: { item in
                (item.status == .sending || item.status == .sent)
                && item.replyMessageId == nil

            }, build: { item, collectionView, indexPath in
                let cell: OutgoingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.build(bubble: cell.rightView, with: item)
                cell.canReply = item.status == .sent
                cell.performReply = performReply
                cell.rightView.didTapShowRound = { showRound(item.roundURL) }

                return cell
            }
        )
    }

    static func outgoingFailedText(performReply: @escaping () -> Void) -> Self {
        .init(
            canBuild: { item in
                (item.status == .sendingFailed || item.status == .sendingTimedOut)
                && item.replyMessageId == nil

            }, build: { item, collectionView, indexPath in
                let cell: OutgoingFailedTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

                Bubbler.build(bubble: cell.rightView, with: item)
                cell.canReply = false
                cell.performReply = performReply
                return cell
            }
        )
    }
}

struct ActionFactory {
    enum Action {
        case copy
        case retry
        case reply
        case delete
        case report

        var title: String {
            switch self {

            case .copy:
                return Localized.Chat.BubbleMenu.copy
            case .retry:
                return Localized.Chat.BubbleMenu.retry
            case .reply:
                return Localized.Chat.BubbleMenu.reply
            case .delete:
                return Localized.Chat.BubbleMenu.delete
            case .report:
                return Localized.Chat.BubbleMenu.report
            }
        }
    }

    static func build(
        from item: Message,
        action: Action,
        closure: @escaping (Message) -> Void
    ) -> UIAction? {

        switch action {
        case .report:
            guard item.status == .received else { return nil }
        case .reply:
            guard item.status == .received || item.status == .sent else { return nil }
        case .retry:
            guard item.status == .sendingFailed || item.status == .sendingTimedOut else { return nil }
        case .delete, .copy:
            break
        }

        return UIAction(
            title: action.title,
            state: .off,
            handler: { _ in closure(item) }
        )
    }
}