//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
import LibSignalClient

@objc
public class AuthedAccount: NSObject {

    public typealias DeviceId = AuthedDevice.DeviceId

    public struct Explicit: Equatable {
        public let aci: Aci
        public let pni: Pni
        public let e164: E164
        public let deviceId: DeviceId
        public let authPassword: String

        public init(
            aci: Aci,
            pni: Pni,
            e164: E164,
            deviceId: DeviceId,
            authPassword: String
        ) {
            self.aci = aci
            self.pni = pni
            self.e164 = e164
            self.deviceId = deviceId
            self.authPassword = authPassword
        }
    }

    public enum Info: Equatable {
        case implicit
        case explicit(Explicit)
    }

    @nonobjc
    public let info: Info

    private init(_ info: Info) {
        self.info = info
        super.init()
    }

    /// Will use info present on TSAccountManager
    @objc
    public static func implicit() -> AuthedAccount {
        return AuthedAccount(.implicit)
    }

    public static func explicit(
        aci: Aci,
        pni: Pni,
        e164: E164,
        deviceId: DeviceId,
        authPassword: String
    ) -> AuthedAccount {
        return AuthedAccount(.explicit(Explicit(
            aci: aci,
            pni: pni,
            e164: e164,
            deviceId: deviceId,
            authPassword: authPassword
        )))
    }

    public override var hash: Int {
        var hasher = Hasher()
        switch info {
        case .implicit:
            break
        case let .explicit(info):
            hasher.combine(info.aci)
            hasher.combine(info.e164)
            hasher.combine(info.authPassword)
        }
        return hasher.finalize()
    }

    public override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? AuthedAccount else {
            return false
        }
        return self.info == other.info
    }

    public func orIfImplicitUse(_ other: AuthedAccount) -> AuthedAccount {
        switch (self.info, other.info) {
        case (.explicit, _):
            return self
        case (_, .explicit):
            return other
        case (.implicit, .implicit):
            return other
        }
    }

    @objc
    public func isAddressForLocalUser(_ address: SignalServiceAddress) -> Bool {
        switch info {
        case .implicit:
            return false
        case let .explicit(info):
            return info.isAddressForLocalUser(address)
        }
    }

    @objc
    public var chatServiceAuth: ChatServiceAuth {
        switch info {
        case .implicit:
            return .implicit()
        case let .explicit(info):
            return info.chatServiceAuth
        }
    }

    public func authedDevice(isPrimaryDevice: Bool) -> AuthedDevice {
        switch info {
        case .implicit:
            return .implicit
        case let .explicit(info):
            return .explicit(AuthedDevice.Explicit(
                aci: info.aci,
                phoneNumber: info.e164,
                pni: info.pni,
                deviceId: info.deviceId,
                authPassword: info.authPassword
            ))
        }
    }
}

extension AuthedAccount.Explicit {

    public func isAddressForLocalUser(_ address: SignalServiceAddress) -> Bool {
        return localIdentifiers.contains(address: address)
    }

    public var localIdentifiers: LocalIdentifiers {
        return LocalIdentifiers(aci: aci, pni: pni, phoneNumber: e164.stringValue)
    }

    public var chatServiceAuth: ChatServiceAuth {
        return .explicit(aci: aci, deviceId: deviceId, password: authPassword)
    }
}
