import AppCore
import ComposableArchitecture
import Foundation
import XXMessengerClient
import XXModels

public struct GroupComponent: ReducerProtocol {
  public struct State: Equatable {
    public init(
      groupId: XXModels.Group.ID,
      groupInfo: XXModels.GroupInfo? = nil,
      isJoining: Bool = false,
      joinFailure: String? = nil
    ) {
      self.groupId = groupId
      self.groupInfo = groupInfo
      self.isJoining = isJoining
      self.joinFailure = joinFailure
    }

    public var groupId: XXModels.Group.ID
    public var groupInfo: XXModels.GroupInfo?
    public var isJoining: Bool
    public var joinFailure: String?
  }

  public enum Action: Equatable {
    case start
    case didFetchGroupInfo(XXModels.GroupInfo?)
    case joinButtonTapped
    case didJoin
    case didFailToJoin(String)
  }

  public init() {}

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

  public var body: some ReducerProtocol<State, Action> {
    Reduce { state, action in
      switch action {
      case .start:
        return Effect
          .catching { try db() }
          .flatMap { [state] in
            let query = GroupInfo.Query(groupId: state.groupId)
            return $0.fetchGroupInfosPublisher(query).map(\.first)
          }
          .assertNoFailure()
          .map(Action.didFetchGroupInfo)
          .subscribe(on: bgQueue)
          .receive(on: mainQueue)
          .eraseToEffect()

      case .didFetchGroupInfo(let groupInfo):
        state.groupInfo = groupInfo
        return .none

      case .joinButtonTapped:
        guard let info = state.groupInfo else { return .none }
        state.isJoining = true
        state.joinFailure = nil
        return Effect.result {
          do {
            let groupChat = try messenger.groupChat.tryGet()
            try groupChat.joinGroup(serializedGroupData: info.group.serialized)
            var group = info.group
            group.authStatus = .participating
            try db().saveGroup(group)
            return .success(.didJoin)
          } catch {
            return .success(.didFailToJoin(error.localizedDescription))
          }
        }
        .subscribe(on: bgQueue)
        .receive(on: mainQueue)
        .eraseToEffect()

      case .didJoin:
        state.isJoining = false
        state.joinFailure = nil
        return .none

      case .didFailToJoin(let failure):
        state.isJoining = false
        state.joinFailure = failure
        return .none
      }
    }
  }
}