import math
import os
import sys
import random
import time
import json
import pika
import requests
import ssl
from datetime import datetime, timezone
from dotenv import load_dotenv


class RabbitBackend(object):

    def __init__(self, broker, user, passwd, ca=None, client_cert=None, client_key=None):
        host, port = broker.split(":")
        self.host = host
        self.port = int(port)
        self.user = user
        self.passwd = passwd
        self.credentials = pika.PlainCredentials(self.user, self.passwd)
        self.ca = ca
        self.client_cert = client_cert
        self.client_key = client_key

    def get_connection(self, vhost="/"):
        if self.ca:
            context = ssl.create_default_context(cafile=self.ca)
            context.verify_mode = ssl.CERT_REQUIRED
        else:
            context = ssl.create_default_context()
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE
        if self.client_cert and self.client_key:
            context.load_cert_chain(certfile=self.client_cert, keyfile=self.client_key)
        return pika.BlockingConnection(pika.ConnectionParameters(
            self.host,
            self.port,
            vhost,
            self.credentials,
            ssl_options=pika.SSLOptions(context)
        ))


def calculate_stats(data):
    # sleep a random amount of time to simulate longer processign time.
    time.sleep(random.randint(5, 60))
    return {
        "mean": math.fsum(data) / len(data)
    }
    
def send_notification(channel, sensor):
    channel.basic_publish(
        exchange="notifications",
        routing_key="",
        body=json.dumps({
            "task": "statistics",
            "sensor": sensor,
        })
    )

def log_error(channel, message):
    channel.basic_publish(
        exchange="logs",
        routing_key="",
        body=json.dumps({
            "timestamp": datetime.now().isoformat(),
            "content": message
        })
    )
    
def handle_task(channel, method, properties, body):
    print("Handling task")
    try:
        # try to parse data and return address from the message body
        task = json.loads(body)
        data = task["data"]
        sensor = task["sensor"]
        href = API_SERVER + f"/api/sensors/{sensor}/stats/"
    except (KeyError, json.JSONDecodeError) as e:
        print(e)
        log_error(channel, f"Task parse error: {e}")
    else:
        # calculate stats
        stats = calculate_stats(task["data"])
        stats["generated"] = datetime.now(timezone.utc).isoformat()
    
        # send the results back to the API
        with requests.Session() as session:
            resp = session.put(
                href,
                json=stats
            )
    
        if resp.status_code != 204:
            # log error 
            log_error(channel, f"Unable to send result")
        else:
            send_notification(channel, sensor)
    finally:
        # acknowledge the task regardless of outcome
        print("Task handled")
        channel.basic_ack(delivery_tag=method.delivery_tag)

def main(backend):
    connection = backend.get_connection(os.environ["PWP_RABBIT_VHOST"])
    channel = connection.channel()
    channel.exchange_declare(
        exchange="notifications",
        exchange_type="fanout"
    )
    channel.exchange_declare(
        exchange="logs",
        exchange_type="fanout"
    )
    channel.queue_declare(queue="stats")
    channel.basic_consume(queue="stats", on_message_callback=handle_task)
    print("Service started")
    channel.start_consuming()
    
if __name__ == "__main__":
    try:
        load_dotenv(os.getenv(sys.argv[1]))
        rabbit = RabbitBackend(
            broker=os.environ["PWP_RABBIT_URI"],
            user=os.environ["PWP_RABBIT_USER"],
            passwd=os.environ["PWP_RABBIT_PASSWD"],
            ca=os.environ.get("CA_CERT"),
            client_cert=os.environ.get("PWP_RABBIT_CLIENT_CERT"),
            client_key=os.environ.get("PWP_RABBIT_CLIENT_KEY")
        )
        API_SERVER = os.environ["API_SERVER"]
    except IndexError:
        print("Dotenv file path must be supplied")
    except KeyError as e:
        print("Missing required configuration parameter: {e}")
    else:

        try:
            main(rabbit)
        except KeyboardInterrupt:
            try:
                sys.exit(0)
            except SystemExit:
                os._exit(0)
