import UIKit
import Shared
import Combine
import AppCore
import XXModels
import Defaults
import XXClient
import Dependencies
import CombineSchedulers
import XXMessengerClient

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(\.app.dbManager) var dbManager
  @Dependency(\.app.messenger) var messenger
  @Dependency(\.hudManager) var hudManager
  
  @KeyObject(.username, defaultValue: nil) var username: String?
  @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
  @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
  
  var contact: XXModels.Contact
  
  var popPublisher: AnyPublisher<Void, Never> { popRelay.eraseToAnyPublisher() }
  var successPublisher: AnyPublisher<Void, Never> { successRelay.eraseToAnyPublisher() }
  var popToRootPublisher: AnyPublisher<Void, Never> { popToRootRelay.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 stateRelay = CurrentValueSubject<ContactViewState, Never>(.init())
  
  var myId: Data {
    try! messenger.e2e.get()!.getContact().getId()
  }
  
  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
  
  init(_ contact: XXModels.Contact) {
    self.contact = contact

    stateRelay.value = .init(
      title: contact.nickname ?? contact.username,
      email: contact.email,
      phone: contact.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? dbManager.getDB().saveContact(contact)
  }
  
  func didTapDelete() {
    hudManager.show()
    
    do {
      try messenger.e2e.get()!.deleteRequest.partnerId(contact.id)
      try dbManager.getDB().deleteContact(contact)
      
      hudManager.hide()
      popToRootRelay.send()
    } catch {
      hudManager.show(.init(error: error))
    }
  }
  
  func didTapReject() {
    // TODO: Reject function on the API?
    _ = try? dbManager.getDB().deleteContact(contact)
    popRelay.send()
  }
  
  func didTapClear() {
    _ = try? dbManager.getDB().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? dbManager.getDB().saveContact(contact)
    
    stateRelay.value.nickname = contact.nickname
  }
  
  func didTapResend() {
    hudManager.show()
    contact.authStatus = .requesting
    
    backgroundScheduler.schedule { [weak self] in
      guard let self else { return }
      
      do {
        try self.dbManager.getDB().saveContact(self.contact)
        
        var includedFacts: [Fact] = []
        let myFacts = try self.messenger.ud.get()!.getFacts()
        
        if let fact = myFacts.get(.username) {
          includedFacts.append(fact)
        }
        
        if self.sharingEmail, let fact = myFacts.get(.email) {
          includedFacts.append(fact)
        }
        
        if self.sharingPhone, let fact = myFacts.get(.phone) {
          includedFacts.append(fact)
        }
        
        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
          partner: .live(self.contact.marshaled!),
          myFacts: includedFacts
        )
        
        self.contact.authStatus = .requested
        try self.dbManager.getDB().saveContact(self.contact)
        
        self.hudManager.hide()
        self.popRelay.send()
      } catch {
        self.contact.authStatus = .requestFailed
        _ = try? self.dbManager.getDB().saveContact(self.contact)
        self.hudManager.show(.init(error: error))
      }
    }
  }
  
  func didTapRequest(with nickname: String) {
    hudManager.show()
    contact.nickname = nickname
    contact.authStatus = .requesting
    
    backgroundScheduler.schedule { [weak self] in
      guard let self else { return }
      
      do {
        try self.dbManager.getDB().saveContact(self.contact)
        
        var includedFacts: [Fact] = []
        let myFacts = try self.messenger.ud.get()!.getFacts()
        
        if let fact = myFacts.get(.username) {
          includedFacts.append(fact)
        }
        
        if self.sharingEmail, let fact = myFacts.get(.email) {
          includedFacts.append(fact)
        }
        
        if self.sharingPhone, let fact = myFacts.get(.phone) {
          includedFacts.append(fact)
        }
        
        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
          partner: .live(self.contact.marshaled!),
          myFacts: includedFacts
        )
        
        self.contact.authStatus = .requested
        try self.dbManager.getDB().saveContact(self.contact)
        
        self.hudManager.hide()
        self.successRelay.send()
      } catch {
        self.contact.authStatus = .requestFailed
        _ = try? self.dbManager.getDB().saveContact(self.contact)
        self.hudManager.show(.init(error: error))
      }
    }
  }
  
  func didTapAccept(_ nickname: String) {
    hudManager.show()
    contact.nickname = nickname
    contact.authStatus = .confirming
    
    backgroundScheduler.schedule { [weak self] in
      guard let self else { return }
      
      do {
        try self.dbManager.getDB().saveContact(self.contact)
        
        let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(self.contact.marshaled!))
        
        self.contact.authStatus = .friend
        try self.dbManager.getDB().saveContact(self.contact)
        
        self.hudManager.hide()
        self.popRelay.send()
      } catch {
        self.contact.authStatus = .confirmationFailed
        _ = try? self.dbManager.getDB().saveContact(self.contact)
        self.hudManager.show(.init(error: error))
      }
    }
  }
}