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

import Foundation
import GRDB
import LibSignalClient

public enum VerificationState: Equatable {
    case verified
    case noLongerVerified
    case implicit(isAcknowledged: Bool)

    init(_ verificationState: OWSVerificationState) {
        switch verificationState {
        case .default:
            self = .implicit(isAcknowledged: false)
        case .defaultAcknowledged:
            self = .implicit(isAcknowledged: true)
        case .verified:
            self = .verified
        case .noLongerVerified:
            self = .noLongerVerified
        }
    }

    var rawValue: OWSVerificationState {
        switch self {
        case .implicit(isAcknowledged: false):
            return .default
        case .implicit(isAcknowledged: true):
            return .defaultAcknowledged
        case .verified:
            return .verified
        case .noLongerVerified:
            return .noLongerVerified
        }
    }
}

extension OWSRecipientIdentity {
    public var identityKeyObject: IdentityKey {
        get throws {
            try IdentityKey(publicKey: PublicKey(keyData: identityKey))
        }
    }

    public class func groupContainsUnverifiedMember(
        _ threadUniqueId: String,
        transaction: SDSAnyReadTransaction
    ) -> Bool {
        let identityKeys = groupMemberIdentityKeys(
            in: threadUniqueId,
            matching: .verified,
            negated: true,
            limit: 1,
            tx: transaction
        )
        return !identityKeys.isEmpty
    }

    public class func noLongerVerifiedIdentityKeys(
        in threadUniqueId: String,
        tx: SDSAnyReadTransaction
    ) -> [SignalServiceAddress: Data] {
        return groupMemberIdentityKeys(in: threadUniqueId, matching: .noLongerVerified, negated: false, tx: tx)
    }

    private class func sqlQueryToFetchIdentityKeys(
        matching verificationState: OWSVerificationState,
        negated: Bool,
        limit: Int
    ) -> String {
        let limitClause: String
        if limit < Int.max {
            limitClause = "LIMIT \(limit)"
        } else {
            limitClause = ""
        }
        let comparisonOperator = negated ? "!=" : "="
        let stateClause = "\(recipientIdentityColumnFullyQualified: .verificationState) \(comparisonOperator) \(verificationState.rawValue)"

        let groupMember_phoneNumber = TSGroupMember.columnName(.phoneNumber, fullyQualified: true)
        let groupMember_groupThreadID = TSGroupMember.columnName(.groupThreadId, fullyQualified: true)
        let groupMember_serviceIdString = TSGroupMember.columnName(.serviceId, fullyQualified: true)

        let recipient_id = "\(signalRecipientColumnFullyQualified: .id)"
        let recipient_phoneNumber = "\(signalRecipientColumnFullyQualified: .phoneNumber)"
        let recipient_aciString = "\(signalRecipientColumnFullyQualified: .aciString)"
        let recipient_pniString = "\(signalRecipientColumnFullyQualified: .pni)"
        let recipient_uniqueID = "\(signalRecipientColumnFullyQualified: .uniqueId)"

        let recipientIdentity_uniqueID = "\(recipientIdentityColumnFullyQualified: .uniqueId)"
        let recipientIdentity_identityKey = "\(recipientIdentityColumnFullyQualified: .identityKey)"
        let exceptClause = "\(recipient_aciString) != ?"
        let sql =
        """
            SELECT \(recipient_aciString), \(recipient_phoneNumber), \(recipient_pniString), \(recipientIdentity_identityKey)
            FROM
                \(SignalRecipient.databaseTableName),
                \(RecipientIdentityRecord.databaseTableName),
                \(TSGroupMember.databaseTableName)
            WHERE
                \(recipient_uniqueID) = \(recipientIdentity_uniqueID)
                AND \(groupMember_groupThreadID) = ?
                AND (
                    \(groupMember_serviceIdString) = \(recipient_aciString)
                    OR \(groupMember_serviceIdString) = \(recipient_pniString)
                    OR \(groupMember_phoneNumber) = \(recipient_phoneNumber)
                )
                AND \(exceptClause)
                AND \(stateClause)
            ORDER BY \(recipient_id)
            \(limitClause)
        """
        return sql
    }

    private class func groupMemberIdentityKeys(
        in threadUniqueId: String,
        matching verificationState: OWSVerificationState,
        negated: Bool,
        limit: Int = Int.max,
        tx: SDSAnyReadTransaction
    ) -> [SignalServiceAddress: Data] {
        switch tx.readTransaction {
        case .grdbRead(let grdbTransaction):
            // There should always be a recipient UUID, but just in case there isn't provide a fake value that won't
            // affect the results of the query.
            let localRecipientAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx.asV2Read)?.aci
            let sql = sqlQueryToFetchIdentityKeys(matching: verificationState, negated: negated, limit: limit)
            do {
                let args = [threadUniqueId, localRecipientAci?.serviceIdUppercaseString ?? "fake"]
                let cursor = try Row.fetchCursor(grdbTransaction.database, sql: sql, arguments: StatementArguments(args))
                var result = [SignalServiceAddress: Data]()
                while let row = try cursor.next() {
                    let normalizedAddress = NormalizedDatabaseRecordAddress(
                        aci: (row[0] as String?).flatMap { try? Aci.parseFrom(serviceIdString: $0) },
                        phoneNumber: row[1],
                        pni: (row[2] as String?).flatMap { try? Pni.parseFrom(serviceIdString: $0) }
                    )
                    let address = SignalServiceAddress(
                        serviceId: normalizedAddress?.serviceId,
                        phoneNumber: normalizedAddress?.phoneNumber
                    )
                    result[address] = row[3]
                }
                return result
            } catch {
                owsFailDebug("error: \(error)")
                return [:]
            }
        }
    }
}
