import AppCore
import ComposableArchitecture
import Foundation
import XCTestDynamicOverlay
import XXClient
import XXMessengerClient
import XXModels

public struct RegisterComponent: ReducerProtocol {
  public struct State: Equatable {
    public enum Error: Swift.Error, Equatable {
      case usernameMismatch(registering: String, registered: String?)
    }

    public enum Field: String, Hashable {
      case username
    }

    public init(
      focusedField: Field? = nil,
      username: String = "",
      isRegistering: Bool = false,
      failure: String? = nil
    ) {
      self.focusedField = focusedField
      self.username = username
      self.isRegistering = isRegistering
      self.failure = failure
    }

    @BindableState public var focusedField: Field?
    @BindableState public var username: String
    public var isRegistering: Bool
    public var failure: String?
  }

  public enum Action: Equatable, BindableAction {
    case registerTapped
    case failed(String)
    case finished
    case binding(BindingAction<State>)
  }

  public init() {}

  @Dependency(\.app.messenger) var messenger: Messenger
  @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
  @Dependency(\.app.now) var now: () -> Date
  @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>

  public var body: some ReducerProtocol<State, Action> {
    BindingReducer()
    Reduce { state, action in
      switch action {
      case .binding(_):
        return .none

      case .registerTapped:
        state.focusedField = nil
        state.isRegistering = true
        state.failure = nil
        return .future { [username = state.username] fulfill in
          do {
            let db = try db()
            try messenger.register(username: username)
            let contact = try messenger.myContact()
            let facts = try contact.getFacts()
            try db.saveContact(Contact(
              id: try contact.getId(),
              marshaled: contact.data,
              username: facts.get(.username)?.value,
              email: facts.get(.email)?.value,
              phone: facts.get(.phone)?.value,
              createdAt: now()
            ))
            guard facts.get(.username)?.value == username else {
              throw State.Error.usernameMismatch(
                registering: username,
                registered: facts.get(.username)?.value
              )
            }
            fulfill(.success(.finished))
          }
          catch {
            fulfill(.success(.failed(error.localizedDescription)))
          }
        }
        .subscribe(on: bgQueue)
        .receive(on: mainQueue)
        .eraseToEffect()

      case .failed(let failure):
        state.isRegistering = false
        state.failure = failure
        return .none

      case .finished:
        state.isRegistering = false
        return .none
      }
    }
  }
}