/*****************************************************************************
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*
*****************************************************************************/

#define NSCORE 1
#include "config.h"
#include "comments.h"
#include "common.h"
#include "statusdata.h"
#include "downtime.h"
#include "macros.h"
#include "nagios.h"
#include "broker.h"
#include "perfdata.h"
#include "tap.h"
#include "test-stubs.c"
#include "stub_sehandlers.c"
#include "stub_comments.c"
#include "stub_perfdata.c"
#include "stub_downtime.c"
#include "../common/shared.c"

int log_host_retries = 0;
int date_format;

/* Test specific functions + variables */
service *svc1 = NULL, *svc2 = NULL;
host *host1 = NULL;
int found_log_rechecking_host_when_service_wobbles = 0;
int found_log_run_async_host_check_3x = 0;
check_result *tmp_check_result;

void setup_check_result() {
	struct timeval start_time, finish_time;
	start_time.tv_sec = 1234567890L;
	start_time.tv_usec = 0L;
	finish_time.tv_sec = 1234567891L;
	finish_time.tv_usec = 0L;

	tmp_check_result = (check_result *)malloc(sizeof(check_result));
	tmp_check_result->check_type = SERVICE_CHECK_ACTIVE;
	tmp_check_result->check_options = 0;
	tmp_check_result->scheduled_check = TRUE;
	tmp_check_result->reschedule_check = TRUE;
	tmp_check_result->exited_ok = TRUE;
	tmp_check_result->return_code = 0;
	tmp_check_result->output = strdup("Fake result");
	tmp_check_result->latency = 0.6969;
	tmp_check_result->start_time = start_time;
	tmp_check_result->finish_time = finish_time;
	}

int c = 0;
int update_program_status(int aggregated_dump) {
	c++;
	/* printf ("# In the update_program_status hook: %d\n", c); */

	/* Set this to break out of event_execution_loop */
	if(c > 10) {
		sigshutdown = TRUE;
		c = 0;
		}
	}
int log_debug_info(int level, int verbosity, const char *fmt, ...) {
	va_list ap;
	char *buffer = NULL;

	va_start(ap, fmt);
	/* vprintf( fmt, ap ); */
	vasprintf(&buffer, fmt, ap);
	if(strcmp(buffer, "Service wobbled between non-OK states, so we'll recheck the host state...\n") == 0) {
		found_log_rechecking_host_when_service_wobbles++;
		}
	if(strcmp(buffer, "run_async_host_check_3x()\n") == 0) {
		found_log_run_async_host_check_3x++;
		}
	free(buffer);
	va_end(ap);
	}


void
setup_objects(time_t time) {
	timed_event *new_event = NULL;

	enable_predictive_service_dependency_checks = FALSE;

	host1 = (host *)calloc(1, sizeof(host));
	host1->name = strdup("Host1");
	host1->address = strdup("127.0.0.1");
	host1->retry_interval = 1;
	host1->check_interval = 5;
	host1->check_options = 0;
	host1->state_type = SOFT_STATE;
	host1->current_state = HOST_DOWN;
	host1->has_been_checked = TRUE;
	host1->last_check = time;
	host1->next_check = time;

	/* First service is a normal one */
	svc1 = (service *)calloc(1, sizeof(service));
	svc1->host_name = strdup("Host1");
	svc1->host_ptr = host1;
	svc1->description = strdup("Normal service");
	svc1->check_options = 0;
	svc1->next_check = time;
	svc1->state_type = SOFT_STATE;
	svc1->current_state = STATE_CRITICAL;
	svc1->retry_interval = 1;
	svc1->check_interval = 5;
	svc1->current_attempt = 1;
	svc1->max_attempts = 4;
	svc1->last_state_change = 0;
	svc1->last_state_change = 0;
	svc1->last_check = (time_t)1234560000;
	svc1->host_problem_at_last_check = FALSE;
	svc1->plugin_output = strdup("Initial state");
	svc1->last_hard_state_change = (time_t)1111111111;

	/* Second service .... to be configured! */
	svc2 = (service *)calloc(1, sizeof(service));
	svc2->host_name = strdup("Host1");
	svc2->description = strdup("To be nudged");
	svc2->check_options = 0;
	svc2->next_check = time;
	svc2->state_type = SOFT_STATE;
	svc2->current_state = STATE_OK;
	svc2->retry_interval = 1;
	svc2->check_interval = 5;

	}

int
main(int argc, char **argv) {
	time_t now = 0L;


	plan_tests(42);

	time(&now);


	/* Test to confirm that if a service is warning, the notified_on_critical is reset */
	tmp_check_result = (check_result *)calloc(1, sizeof(check_result));
	tmp_check_result->host_name = strdup("host1");
	tmp_check_result->service_description = strdup("Normal service");
	tmp_check_result->object_check_type = SERVICE_CHECK;
	tmp_check_result->check_type = SERVICE_CHECK_ACTIVE;
	tmp_check_result->check_options = 0;
	tmp_check_result->scheduled_check = TRUE;
	tmp_check_result->reschedule_check = TRUE;
	tmp_check_result->latency = 0.666;
	tmp_check_result->start_time.tv_sec = 1234567890;
	tmp_check_result->start_time.tv_usec = 56565;
	tmp_check_result->finish_time.tv_sec = 1234567899;
	tmp_check_result->finish_time.tv_usec = 45454;
	tmp_check_result->early_timeout = 0;
	tmp_check_result->exited_ok = TRUE;
	tmp_check_result->return_code = 1;
	tmp_check_result->output = strdup("Warning - check notified_on_critical reset");

	setup_objects(now);
	svc1->last_state = STATE_CRITICAL;
	svc1->notified_on_critical = TRUE;
	svc1->current_notification_number = 999;
	svc1->last_notification = (time_t)11111;
	svc1->next_notification = (time_t)22222;
	svc1->no_more_notifications = TRUE;

	handle_async_service_check_result(svc1, tmp_check_result);

	/* This has been taken out because it is not required
	ok( svc1->notified_on_critical==FALSE, "notified_on_critical reset" );
	*/
	ok(svc1->last_notification == (time_t)0, "last notification reset due to state change");
	ok(svc1->next_notification == (time_t)0, "next notification reset due to state change");
	ok(svc1->no_more_notifications == FALSE, "no_more_notifications reset due to state change");
	ok(svc1->current_notification_number == 999, "notification number NOT reset");

	/* Test case:
		service that transitions from OK to CRITICAL (where its host is set to DOWN) will get set to a hard state
		even though check attempts = 1 of 4
	*/
	setup_objects((time_t) 1234567800L);
	host1->current_state = HOST_DOWN;
	svc1->current_state = STATE_OK;
	svc1->state_type = HARD_STATE;
	setup_check_result();
	tmp_check_result->return_code = STATE_CRITICAL;
	tmp_check_result->output = strdup("CRITICAL failure");
	log_service_event_flag = 0;

	handle_async_service_check_result(svc1, tmp_check_result);

	ok(log_service_event_flag == 1, "log_service_event() was called");
	ok(svc1->last_hard_state_change == (time_t)1234567890, "Got last_hard_state_change time=%lu", svc1->last_hard_state_change);
	ok(svc1->last_state_change == svc1->last_hard_state_change, "Got same last_state_change");
	ok(svc1->last_hard_state == 2, "Should save the last hard state as critical for next time");
	ok(svc1->host_problem_at_last_check == TRUE, "Got host_problem_at_last_check set to TRUE due to host failure - this needs to be saved otherwise extra alerts raised in subsequent runs");
	ok(svc1->state_type == HARD_STATE, "This should be a HARD state since the host is in a failure state");
	ok(svc1->current_attempt == 1, "Previous status was OK, so this failure should show current_attempt=1") || diag("Current attempt=%d", svc1->current_attempt);





	/* Test case:
		OK -> WARNING 1/4 -> ack -> WARNING 2/4 -> OK transition
		Tests that the ack is left for 2/4
	*/
	setup_objects(now);
	host1->current_state = HOST_UP;
	host1->max_attempts = 4;
	svc1->last_state = STATE_OK;
	svc1->last_hard_state = STATE_OK;
	svc1->current_state = STATE_OK;
	svc1->state_type = SOFT_STATE;

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure");
	handle_async_service_check_result(svc1, tmp_check_result);

	ok(svc1->last_notification == (time_t)0, "last notification reset due to state change");
	ok(svc1->next_notification == (time_t)0, "next notification reset due to state change");
	ok(svc1->no_more_notifications == FALSE, "no_more_notifications reset due to state change");
	ok(svc1->current_notification_number == 0, "notification number reset");
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "No acks");

	svc1->acknowledgement_type = ACKNOWLEDGEMENT_NORMAL;

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure");
	handle_async_service_check_result(svc1, tmp_check_result);

	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack left");

	setup_check_result();
	tmp_check_result->return_code = STATE_OK;
	tmp_check_result->output = strdup("Back to OK");
	handle_async_service_check_result(svc1, tmp_check_result);

	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "Ack reset to none");




	/* Test case:
	   OK -> WARNING 1/4 -> ack -> WARNING 2/4 -> WARNING 3/4 -> WARNING 4/4 -> WARNING 4/4 -> OK transition
	   Tests that the ack is not removed on hard state change
	*/
	setup_objects(now);
	host1->current_state = HOST_UP;
	host1->max_attempts = 4;
	svc1->last_state = STATE_OK;
	svc1->last_hard_state = STATE_OK;
	svc1->current_state = STATE_OK;
	svc1->state_type = SOFT_STATE;
	svc1->current_attempt = 1;

	setup_check_result();
	tmp_check_result->return_code = STATE_OK;
	tmp_check_result->output = strdup("Reset to OK");
	handle_async_service_check_result(svc1, tmp_check_result);

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure 1");
	handle_async_service_check_result(svc1, tmp_check_result);

	ok(svc1->state_type == SOFT_STATE, "Soft state");
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "No acks - testing transition to hard warning state");

	svc1->acknowledgement_type = ACKNOWLEDGEMENT_NORMAL;

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure 2");
	handle_async_service_check_result(svc1, tmp_check_result);
	ok(svc1->state_type == SOFT_STATE, "Soft state");
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack left");

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure 3");
	handle_async_service_check_result(svc1, tmp_check_result);
	ok(svc1->state_type == SOFT_STATE, "Soft state");
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack left");

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure 4");
	handle_async_service_check_result(svc1, tmp_check_result);
	ok(svc1->state_type == HARD_STATE, "Hard state");
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack left on hard failure");

	setup_check_result();
	tmp_check_result->return_code = STATE_OK;
	tmp_check_result->output = strdup("Back to OK");
	handle_async_service_check_result(svc1, tmp_check_result);
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "Ack removed");




	/* Test case:
	   OK -> WARNING 1/1 -> ack -> WARNING -> OK transition
	   Tests that the ack is not removed on 2nd warning, but is on OK
	*/
	setup_objects(now);
	host1->current_state = HOST_UP;
	host1->max_attempts = 4;
	svc1->last_state = STATE_OK;
	svc1->last_hard_state = STATE_OK;
	svc1->current_state = STATE_OK;
	svc1->state_type = SOFT_STATE;
	svc1->current_attempt = 1;
	svc1->max_attempts = 2;
	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure 1");

	handle_async_service_check_result(svc1, tmp_check_result);

	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "No acks - testing transition to immediate hard then OK");

	svc1->acknowledgement_type = ACKNOWLEDGEMENT_NORMAL;

	setup_check_result();
	tmp_check_result->return_code = STATE_WARNING;
	tmp_check_result->output = strdup("WARNING failure 2");
	handle_async_service_check_result(svc1, tmp_check_result);
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack left");

	setup_check_result();
	tmp_check_result->return_code = STATE_OK;
	tmp_check_result->output = strdup("Back to OK");
	handle_async_service_check_result(svc1, tmp_check_result);
	ok(svc1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "Ack removed");


	/* Test case:
	   UP -> DOWN 1/4 -> ack -> DOWN 2/4 -> DOWN 3/4 -> DOWN 4/4 -> UP transition
	   Tests that the ack is not removed on 2nd DOWN, but is on UP
	*/
	setup_objects(now);
	host1->current_state = HOST_UP;
	host1->last_state = HOST_UP;
	host1->last_hard_state = HOST_UP;
	host1->state_type = SOFT_STATE;
	host1->current_attempt = 1;
	host1->max_attempts = 4;
	host1->acknowledgement_type = ACKNOWLEDGEMENT_NONE;
	host1->plugin_output = strdup("");
	host1->long_plugin_output = strdup("");
	host1->perf_data = strdup("");
	host1->host_check_command = strdup("Dummy command required");
	host1->accept_passive_host_checks = TRUE;
	passive_host_checks_are_soft = TRUE;
	setup_check_result();

	tmp_check_result->return_code = STATE_CRITICAL;
	tmp_check_result->output = strdup("DOWN failure 2");
	tmp_check_result->check_type = HOST_CHECK_PASSIVE;
	handle_async_host_check_result_3x(host1, tmp_check_result);
	ok(host1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "No ack set");
	ok(host1->current_attempt == 2, "Attempts right (not sure why this goes into 2 and not 1)") || diag("current_attempt=%d", host1->current_attempt);
	ok(strcmp(host1->plugin_output, "DOWN failure 2") == 0, "output set") || diag("plugin_output=%s", host1->plugin_output);

	host1->acknowledgement_type = ACKNOWLEDGEMENT_NORMAL;

	tmp_check_result->output = strdup("DOWN failure 3");
	handle_async_host_check_result_3x(host1, tmp_check_result);
	ok(host1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack should be retained as in soft state");
	ok(host1->current_attempt == 3, "Attempts incremented") || diag("current_attempt=%d", host1->current_attempt);
	ok(strcmp(host1->plugin_output, "DOWN failure 3") == 0, "output set") || diag("plugin_output=%s", host1->plugin_output);


	tmp_check_result->output = strdup("DOWN failure 4");
	handle_async_host_check_result_3x(host1, tmp_check_result);
	ok(host1->acknowledgement_type == ACKNOWLEDGEMENT_NORMAL, "Ack should be retained as in soft state");
	ok(host1->current_attempt == 4, "Attempts incremented") || diag("current_attempt=%d", host1->current_attempt);
	ok(strcmp(host1->plugin_output, "DOWN failure 4") == 0, "output set") || diag("plugin_output=%s", host1->plugin_output);


	tmp_check_result->return_code = STATE_OK;
	tmp_check_result->output = strdup("UP again");
	handle_async_host_check_result_3x(host1, tmp_check_result);
	ok(host1->acknowledgement_type == ACKNOWLEDGEMENT_NONE, "Ack reset due to state change");
	ok(host1->current_attempt == 1, "Attempts reset") || diag("current_attempt=%d", host1->current_attempt);
	ok(strcmp(host1->plugin_output, "UP again") == 0, "output set") || diag("plugin_output=%s", host1->plugin_output);


	return exit_status();
	}

