/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

package std.unittest.mock

import std.collection.*

/**
 * Represents the exhaustiveness of verification.
 * Exhaustive - requires every invocation on an object to be listed inside Verify block.
 * Partial - allows to list only some invocations and ignore 'uninteresting' invoactions.
 */
public enum Exhaustiveness {
    Exhaustive | Partial
}

enum Orderedness {
    Ordered | Unordered
}

enum VerificationResult {
    Success | Failure(VerificationFailedReason)
}

func performVerification(statements: Array<VerifyStatement>, orderedness: Orderedness, exhaustiveness: Exhaustiveness) {
    if (statements.isEmpty()) {
        VerifyMisconfiguration().fail(EmptyVerifyBlock)
    }

    let objectIds = collectObjectsIds(statements)

    // we rely on snapshot being a copy and thus not getting modified concurrently
    let logSnapshot = MockFramework.session { session: MockSession =>
        for (st in statements) {
            st.prepareForVerification(orderedness)
        }
        session.invocationLog.snapshotSinceLastClear(objectIds)
    }

    let verificationResult = match ((orderedness, exhaustiveness)) {
        case (Ordered, Exhaustive) => verifyExhaustiveOrdered(statements, logSnapshot)
        case (Unordered, exhaustiveness) => verifyUnordered(exhaustiveness, statements, logSnapshot)
        case _ => internalError("Unsupported modes")
    }

    match (verificationResult) {
        case Failure(reason) =>
            let nameRegistry = MockNameRegistry(
                statements |> map {st: VerifyStatement => st.invocationMatcher} |> collectArray)
            VerificationFailureReport(logSnapshot, nameRegistry).fail(reason)
        case Success => ()
    }
}

func verifyNoInteractions(objectRefs: Array<Object>) {
    ensureDistinctReferences(objectRefs)

    let objectIds = _FRAMEWORK.findObjectIdsByRefs(objectRefs)
    if (objectIds.size != objectRefs.size) {
        illegalInput(
            "Invalid input: arguments to Verify.noInteractions must be mock or spy objects"
        )
    }

    let logSnapshot = MockFramework.session { session: MockSession =>
        session.invocationLog.snapshotSinceLastClear(objectIds)
    }

    let invocations = logSnapshot.invocations()
    if (invocations.isEmpty()) {
        return
    }

    VerificationFailureReport(logSnapshot, MockNameRegistry.EMPTY).fail(
        UnwantedInteractions(invocations)
    )
}

func ensureDistinctReferences(refs: Array<Object>) {
    if (refs.isEmpty()) {
        illegalInput("Invalid input: empty object array")
    }

    for (i in 0..refs.size) {
        for (j in (i + 1)..refs.size) {
            if (refEq(refs[i], refs[j])) {
                illegalInput("Invalid input: several references to the same object")
            }
        }
    }
}

func collectObjectsIds(statements: Array<VerifyStatement>): HashSet<UInt64> {
    let objectIds = HashSet<UInt64>()
    for (statement in statements) {
        objectIds.add(statement.invocationMatcher.stubId.receiverId)
    }
    return objectIds
}

func checkMatchersConsistency(statement: VerifyStatement) {
    for (matcher in statement.invocationMatcher.positionalMatchers) {
        if (let Some(listeningMatcher) <- (matcher as ListeningMatcher)) {
            match (listeningMatcher.valueListener) {
                case None => ()
                case _ =>
                    VerifyMisconfiguration().fail(ArgumentCaptureNotAllowed(statement, matcher))
            }
        }
    }
}