<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Catalog_notifier
{
	protected $CI;
	protected $loaded_runtime_settings = FALSE;
	protected $enabled = FALSE;
	protected $url = '';
	protected $secret = '';
	protected $token = '';
	protected $timeout = 4;
	protected $connect_timeout = 2;
	protected $verify_ssl = TRUE;

	public function __construct()
	{
		$this->CI =& get_instance();
		$this->CI->load->config('catalog');

		$this->enabled = $this->to_bool($this->CI->config->item('catalog_webhook_enabled'));
		$this->url = trim((string) $this->CI->config->item('catalog_webhook_url'));
		$this->secret = (string) $this->CI->config->item('catalog_webhook_secret');
		$this->token = (string) $this->CI->config->item('catalog_webhook_token');
		$this->timeout = max(1, (int) $this->CI->config->item('catalog_webhook_timeout'));
		$this->connect_timeout = max(1, (int) $this->CI->config->item('catalog_webhook_connect_timeout'));
		$this->verify_ssl = $this->to_bool($this->CI->config->item('catalog_webhook_verify_ssl'));
	}

	public function send($event, $payload = array())
	{
		$this->load_runtime_settings();

		if (!$this->enabled || $this->url === '')
		{
			return FALSE;
		}

		$event = trim((string) $event);
		if ($event === '')
		{
			return FALSE;
		}

		$envelope = array(
			'event' => $event,
			'event_id' => $this->generate_event_id(),
			'occurred_at' => date('c'),
			'source' => 'catalogo',
			'data' => is_array($payload) ? $payload : array()
		);

		$json = json_encode($envelope);
		if ($json === FALSE)
		{
			log_message('error', 'Catalog_notifier: no se pudo serializar payload para evento ' . $event);
			return FALSE;
		}

		$timestamp = (string) time();
		$headers = array(
			'Content-Type: application/json',
			'X-Catalog-Event: ' . $event,
			'X-Catalog-Timestamp: ' . $timestamp
		);

		if ($this->token !== '')
		{
			$headers[] = 'X-Catalog-Token: ' . $this->token;
		}

		if ($this->secret !== '')
		{
			$signature = hash_hmac('sha256', $timestamp . '.' . $json, $this->secret);
			$headers[] = 'X-Catalog-Signature: sha256=' . $signature;
		}

		$ok = $this->post_json($this->url, $json, $headers);
		if (!$ok)
		{
			log_message('error', 'Catalog_notifier: fallo enviando evento ' . $event . ' a webhook');
		}

		return $ok;
	}

	protected function post_json($url, $body, $headers)
	{
		if (function_exists('curl_init'))
		{
			$ch = curl_init($url);
			curl_setopt($ch, CURLOPT_POST, TRUE);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
			curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
			curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
			curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl ? 1 : 0);
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl ? 2 : 0);

			$response = curl_exec($ch);
			$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
			$error = curl_error($ch);
			curl_close($ch);

			if ($response === FALSE || $code < 200 || $code >= 300)
			{
				log_message('error', 'Catalog_notifier curl error code=' . $code . ' error=' . $error);
				return FALSE;
			}

			return TRUE;
		}

		$header_string = implode("\r\n", $headers);
		$context_options = array(
			'http' => array(
				'method' => 'POST',
				'header' => $header_string,
				'content' => $body,
				'timeout' => $this->timeout
			),
			'ssl' => array(
				'verify_peer' => $this->verify_ssl,
				'verify_peer_name' => $this->verify_ssl
			)
		);

		$context = stream_context_create($context_options);
		$response = @file_get_contents($url, FALSE, $context);
		if ($response === FALSE)
		{
			return FALSE;
		}

		if (isset($http_response_header) && is_array($http_response_header))
		{
			foreach ($http_response_header as $line)
			{
				if (preg_match('/^HTTP\/\S+\s+(\d{3})/', $line, $m))
				{
					$code = (int) $m[1];
					return ($code >= 200 && $code < 300);
				}
			}
		}

		return TRUE;
	}

	protected function generate_event_id()
	{
		try
		{
			return bin2hex(random_bytes(12));
		}
		catch (Exception $e)
		{
			return sha1(uniqid('evt_', TRUE));
		}
	}

	protected function load_runtime_settings()
	{
		if ($this->loaded_runtime_settings)
		{
			return;
		}
		$this->loaded_runtime_settings = TRUE;

		if (!isset($this->CI->catalog))
		{
			return;
		}

		$catalog = $this->CI->catalog;
		if (!is_object($catalog) || !method_exists($catalog, 'get_setting'))
		{
			return;
		}

		try
		{
			$this->enabled = $this->to_bool($catalog->get_setting('catalog_webhook_enabled', $this->enabled ? '1' : '0'));
			$this->url = trim((string) $catalog->get_setting('catalog_webhook_url', $this->url));
			$this->secret = (string) $catalog->get_setting('catalog_webhook_secret', $this->secret);
			$this->token = (string) $catalog->get_setting('catalog_webhook_token', $this->token);
			$this->timeout = max(1, (int) $catalog->get_setting('catalog_webhook_timeout', (string) $this->timeout));
			$this->connect_timeout = max(1, (int) $catalog->get_setting('catalog_webhook_connect_timeout', (string) $this->connect_timeout));
			$this->verify_ssl = $this->to_bool($catalog->get_setting('catalog_webhook_verify_ssl', $this->verify_ssl ? '1' : '0'));
		}
		catch (Exception $e)
		{
			log_message('error', 'Catalog_notifier: no se pudieron cargar settings runtime: ' . $e->getMessage());
		}
	}

	protected function to_bool($raw)
	{
		if (is_bool($raw))
		{
			return $raw;
		}
		$val = strtolower(trim((string) $raw));
		return in_array($val, array('1', 'true', 'yes', 'on'), TRUE);
	}
}
