import HUD
import UIKit
import Models
import Combine
import XXModels
import Defaults
import CombineSchedulers
import DependencyInjection

import XXClient

struct ContactViewState: Equatable {
    var title: String?
    var email: String?
    var phone: String?
    var photo: UIImage?
    var username: String?
    var nickname: String?
}

final class ContactViewModel {
    @Dependency var e2e: E2E
    @Dependency var database: Database
    @Dependency var userDiscovery: UserDiscovery
    @Dependency var getFactsFromContact: GetFactsFromContact

    @KeyObject(.username, defaultValue: nil) var username: String?

    var contact: Contact

    var popToRootPublisher: AnyPublisher<Void, Never> { popToRootRelay.eraseToAnyPublisher() }
    var popPublisher: AnyPublisher<Void, Never> { popRelay.eraseToAnyPublisher() }
    var hudPublisher: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
    var successPublisher: AnyPublisher<Void, Never> { successRelay.eraseToAnyPublisher() }
    var statePublisher: AnyPublisher<ContactViewState, Never> { stateRelay.eraseToAnyPublisher() }

    private let popRelay = PassthroughSubject<Void, Never>()
    private let popToRootRelay = PassthroughSubject<Void, Never>()
    private let successRelay = PassthroughSubject<Void, Never>()
    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
    private let stateRelay = CurrentValueSubject<ContactViewState, Never>(.init())

    var myId: Data {
        try! GetIdFromContact.live(userDiscovery.getContact())
    }

    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()

    init(_ contact: Contact) {
        self.contact = contact

        let facts = try? getFactsFromContact(contact: contact.marshaled!)
        let email = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact
        let phone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact

        stateRelay.value = .init(
            title: contact.nickname ?? contact.username,
            email: email,
            phone: phone,
            photo: contact.photo != nil ? UIImage(data: contact.photo!) : nil,
            username: contact.username,
            nickname: contact.nickname
        )
    }

    func didChoosePhoto(_ photo: UIImage) {
        stateRelay.value.photo = photo
        contact.photo = photo.jpegData(compressionQuality: 0.0)
        _ = try? database.saveContact(contact)
    }

    func didTapDelete() {
        hudRelay.send(.on)

        do {
            try e2e.deleteRequest.partner(contact.id)
            try database.deleteContact(contact)

            hudRelay.send(.none)
            popToRootRelay.send()
        } catch {
            hudRelay.send(.error(.init(with: error)))
        }
    }

    func didTapReject() {
        // TODO: Reject function on the API?
        _ = try? database.deleteContact(contact)
        popRelay.send()
    }

    func didTapClear() {
        _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
    }

    func didUpdateNickname(_ string: String) {
        contact.nickname = string.isEmpty ? nil : string
        stateRelay.value.title = string.isEmpty ? contact.username : string
        _ = try? database.saveContact(contact)

        stateRelay.value.nickname = contact.nickname
    }

    func didTapResend() {
        hudRelay.send(.on)
        contact.authStatus = .requesting

        backgroundScheduler.schedule { [weak self] in
            guard let self = self else { return }

            do {
                try self.database.saveContact(self.contact)

                var myFacts = try self.userDiscovery.getFacts()
                myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))

                let _ = try self.e2e.requestAuthenticatedChannel(
                    partnerContact: self.contact.id,
                    myFacts: myFacts
                )

                self.contact.authStatus = .requested
                try self.database.saveContact(self.contact)

                self.hudRelay.send(.none)
                self.popRelay.send()
            } catch {
                self.contact.authStatus = .requestFailed
                _ = try? self.database.saveContact(self.contact)
                self.hudRelay.send(.error(.init(with: error)))
            }
        }
    }

    func didTapRequest(with nickname: String) {
        hudRelay.send(.on)
        contact.nickname = nickname
        contact.authStatus = .requesting

        backgroundScheduler.schedule { [weak self] in
            guard let self = self else { return }

            do {
                try self.database.saveContact(self.contact)

                var myFacts = try self.userDiscovery.getFacts()
                myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))

                let _ = try self.e2e.requestAuthenticatedChannel(
                    partnerContact: self.contact.marshaled!,
                    myFacts: myFacts
                )

                self.contact.authStatus = .requested
                try self.database.saveContact(self.contact)

                self.hudRelay.send(.none)
                self.successRelay.send()
            } catch {
                self.contact.authStatus = .requestFailed
                _ = try? self.database.saveContact(self.contact)
                self.hudRelay.send(.error(.init(with: error)))
            }
        }
    }

    func didTapAccept(_ nickname: String) {
        hudRelay.send(.on)
        contact.nickname = nickname
        contact.authStatus = .confirming

        backgroundScheduler.schedule { [weak self] in
            guard let self = self else { return }

            do {
                try self.database.saveContact(self.contact)

                let _ = try self.e2e.confirmReceivedRequest(partnerContact: self.contact.marshaled!)

                self.contact.authStatus = .friend
                try self.database.saveContact(self.contact)

                self.hudRelay.send(.none)
                self.popRelay.send()
            } catch {
                self.contact.authStatus = .confirmationFailed
                _ = try? self.database.saveContact(self.contact)
                self.hudRelay.send(.error(.init(with: error)))
            }
        }
    }
}