import os
import iso8601
import numpy as np

from typing import List
from os import listdir
from os.path import isfile, join

# config:
log_directory = "C:\\Users\\estil\\Downloads\\203\\data\\server\\bundles\\tablemgr"
start_date = iso8601.parse_date("2023-06-12 09:00:00")
end_date = iso8601.parse_date("2023-06-13 03:00:00")


class LogLine(object):
    def __init__(self, thread_number, log_date, message, line_content):
        self.thread_number = thread_number
        self.log_date = log_date
        self.message = message
        self.line_content = line_content


def read_log_files(log_files):
    file_data = ""
    for file_name in log_files:
        with open(file_name, "rb") as f:
            file_data += f.read()

    return file_data.decode("utf-8")


def get_thread_number(line):
    return line.split("-")[4].strip()


def get_order_id(line_content):
    #  type: (str) -> int
    index = line_content.index("\n")
    if index < 0:
        return -1
    index2 = line_content.index("\n", index + 1)
    order_header_line = line_content[index + 1:index2]
    comps = order_header_line.split(",")
    data = comps[0].split(":")
    try:
        return int(data[1].strip())
    except:
        return -1


def get_message(line_content):
    try:
        index = line_content.index("Thread-")
    except ValueError:
        index = line_content.index("MainThread")
    index2 = line_content.index(" - ", index)
    return line_content[index2 + 3:]


def get_log_date(line_content):
    return iso8601.parse_date(line_content[0:23])


def get_log_lines(log_data):
    lines = []
    line_content = ""
    first_line = None
    for line in log_data.split('\n'):
        line_start = line[0:23]
        try:
            line_date = iso8601.parse_date(line_start)
            if line_date < start_date or line_date > end_date:
                continue

            if line_content != "":
                lines.append(LogLine(
                    get_thread_number(first_line),
                    get_log_date(first_line),
                    get_message(line_content),
                    line_content
                ))
                line_content = ""

            first_line = line
        except:
            pass

        line_content += line + "\n"

    if line_content != "":
        lines.append(LogLine(
            get_thread_number(first_line),
            get_log_date(first_line),
            get_message(line_content),
            line_content
        ))

    return lines


def separate_lines_by_thread(log_lines):
    # type: (List[LogLine]) -> List[List[LogLine]]
    line_dict = {}
    for line in log_lines:
        if line.thread_number not in line_dict:
            line_dict[line.thread_number] = []
        line_dict[line.thread_number].append(line)

    return line_dict.values()


def separate_lines_by_order(log_lines):
    # type: (List[LogLine]) -> List[List[LogLine]]
    line_dict = {}
    for line in log_lines:
        if line.order_id not in line_dict:
            line_dict[line.order_id] = []
        line_dict[line.order_id].append(line)

    return line_dict


def get_connection_id(message):
    index = message.index("c_void_p(")
    index2 = message.index(")", index)
    return message[index + 9:index2]


def main():
    log_files = [os.path.join(log_directory, f) for f in listdir(log_directory) if isfile(join(log_directory, f)) and ".log" in f]
    log_lines = get_log_lines(read_log_files(log_files))

    separated_lines = separate_lines_by_thread(log_lines)

    opened_connections = {}
    connections_with_transaction = {}
    message_times = {}
    message_max_times = {}

    query_times = {}
    query_max_times = {}

    start_time = None
    message = None
    start_line = None

    query_name = None
    start_list_table = None

    print("Start Logging Analisys...")
    for thread_logs in separated_lines:
        for line in thread_logs:
            line.message = line.message.strip()

            if line.message.startswith("Opening connection: "):
                opened_connections[get_connection_id(line.message)] = ""

            elif line.message.startswith("Closing connection: "):
                connection_id = get_connection_id(line.message)
                if connection_id not in opened_connections:
                    print("Connection {} not opened".format(connection_id))
                else:
                    del opened_connections[connection_id]

            elif line.message.startswith("Starting a transaction: "):
                connections_with_transaction[get_connection_id(line.message)] = ""

            elif line.message.startswith("Ending a transaction: "):
                connection_id = get_connection_id(line.message)
                if connection_id not in connections_with_transaction:
                    print("Transaction for connection {} not opened".format(connection_id))
                else:
                    del connections_with_transaction[connection_id]

            elif line.message.startswith("Handling: "):
                start_time = line.log_date
                start_line = line
                message = line.message[10:].strip().split('[')[0]

            elif line.message.startswith("Successfully processed: "):
                end_time = line.log_date

                if end_time is not None and start_time is not None:
                    elapsed_time = (end_time - start_time).total_seconds()
                    if elapsed_time > 60:
                        print(start_line)

                    if elapsed_time > 0:
                        if message not in message_times:
                            message_times[message] = []

                        if message not in message_max_times:
                            message_max_times[message] = elapsed_time
                        elif elapsed_time > message_max_times[message]:
                            message_max_times[message] = elapsed_time
                        message_times[message].append(elapsed_time)
                start_time = None

            elif line.message.startswith("Executing"):
                start_list_table = line.log_date
                query_name = line.message.split('-')[-1]

            elif line.message.startswith("Executed"):
                end_time_list_table = line.log_date
                if end_time_list_table is not None and start_list_table is not None:
                    elapsed_time = (end_time_list_table - start_list_table).total_seconds()
                    if elapsed_time > 0:
                        if query_name not in query_times:
                            query_times[query_name] = []

                        if query_name not in query_max_times:
                            query_max_times[query_name] = elapsed_time
                        elif elapsed_time > query_max_times[query_name]:
                            query_max_times[query_name] = elapsed_time
                        query_times[query_name].append(elapsed_time)
                start_list_table = None

        if len(opened_connections) > 0:
            print("Unclosed connections: {}".format(opened_connections))

        if len(connections_with_transaction) > 0:
            print("Unfinished transactions: {}".format(opened_connections))

    messages = message_times.keys()
    messages.sort()
    for message in messages:
        times = message_times[message]
        a = np.array(times)
        p = np.percentile(a, 95)  # return 95th percentile
        print("{:25} - [{:>4}]: {:>6.3f} / {:>6.3f}".format(message, len(times), np.average(times), p))
    print("")

    messages = query_times.keys()
    messages.sort()
    for query in messages:
        times = query_times[query]
        a = np.array(times)
        p = np.percentile(a, 98)  # return 95th percentile
        if float(p) > 0.020:
            print("{:35} - [{:>4}]: {:>6.3f} / {:>6.3f}".format(query, len(times), np.average(times), p))
    print("")

    for message in message_max_times:
        print("{:40}: {:>6.3f}".format(message, message_max_times[message]))
    print("")

    for query in query_max_times:
        if float(query_max_times[query]) > 0.020:
            print("{:40}: {:>6.3f}".format(query, query_max_times[query]))
    print("")


main()
