use std::sync::{atomic::AtomicBool, Arc};

use tokio::sync::Notify;

use crate::{verification::VerificationReport, Match, Mock, Request, ResponseTemplate};

/// Given the behaviour specification as a [`Mock`], keep track of runtime information
/// concerning this mock - e.g. how many times it matched on a incoming request.
pub(crate) struct MountedMock {
    pub(crate) specification: Mock,
    n_matched_requests: u64,
    /// The position occupied by this mock within the parent [`MountedMockSet`](crate::mock_set::MountedMockSet)
    /// collection of `MountedMock`s.
    ///
    /// E.g. `0` if this is the first mock that we try to match against an incoming request, `1`
    /// if it is the second, etc.
    position_in_set: usize,

    // matched requests:
    matched_requests: Vec<crate::Request>,

    notify: Arc<(Notify, AtomicBool)>,
}

impl MountedMock {
    pub(crate) fn new(specification: Mock, position_in_set: usize) -> Self {
        Self {
            specification,
            n_matched_requests: 0,
            position_in_set,
            matched_requests: Vec::new(),
            notify: Arc::new((Notify::new(), AtomicBool::new(false))),
        }
    }

    /// This is NOT the same of `matches` from the `Match` trait!
    /// Key difference: we are talking a mutable reference to `self` in order to capture
    /// additional information (e.g. how many requests we matched so far) or change behaviour
    /// after a certain threshold has been crossed (e.g. start returning `false` for all requests
    /// once enough requests have been matched according to `max_n_matches`).
    pub(crate) fn matches(&mut self, request: &Request) -> bool {
        if Some(self.n_matched_requests) == self.specification.max_n_matches {
            // Skip the actual check if we are already at our maximum of matched requests.
            false
        } else {
            let matched = self
                .specification
                .matchers
                .iter()
                .all(|matcher| matcher.matches(request));

            if matched {
                // Increase match count
                self.n_matched_requests += 1;
                // Keep track of request
                self.matched_requests.push(request.clone());

                // notification of satisfaction
                if self.verify().is_satisfied() {
                    // always set the satisfaction flag **before** raising the event
                    self.notify
                        .1
                        .store(true, std::sync::atomic::Ordering::Release);
                    self.notify.0.notify_waiters();
                }
            }

            matched
        }
    }

    /// Verify if this mock has verified the expectations set at creation time
    /// over the number of invocations.
    pub(crate) fn verify(&self) -> VerificationReport {
        VerificationReport {
            mock_name: self.specification.name.clone(),
            n_matched_requests: self.n_matched_requests,
            expectation_range: self.specification.expectation_range.clone(),
            position_in_set: self.position_in_set,
        }
    }

    pub(crate) fn response_template(&self, request: &Request) -> ResponseTemplate {
        self.specification.response_template(request)
    }

    pub(crate) fn received_requests(&self) -> Vec<crate::Request> {
        self.matched_requests.clone()
    }

    pub(crate) fn notify(&self) -> Arc<(Notify, AtomicBool)> {
        self.notify.clone()
    }
}
