import os
import argparse
import re
from datetime import datetime

parser = argparse.ArgumentParser(
                    prog='nginx_log_stats',
                    description='gives you a statsitcal view of NGINX requests from an access.log',
                    epilog='For support, contact Quinn (https://github.com/qpxdesign)')


parser.add_argument('-f', '--file', help='file to search in (your NGINX access.log)')
parser.add_argument('-s', '--search', help='General search term to match specific log lines (like for User Agents, specific IPs, etc), either plaintext or regex')
parser.add_argument('-b', '--start_date',help='find logs within given timespan, provide like 08/Nov/2023:08:25:12')
parser.add_argument('-e','--end_date', help='provide like 08/Nov/2023:08:25:12')
parser.add_argument('-w', '--host')
parser.add_argument('-r', '--request')
parser.add_argument('-st', '--status')
parser.add_argument('-a', '--analytics',help='See analytical view of of log selection', action='store_true')
parser.add_argument('-u','--unique',help='use this to only show one entry for every ip',action='store_true')

args = parser.parse_args()

if args.file == None:
    raise Exception("File must be provided (your access.log).")

def keep_log(line):
    parsed_line = parse_line(line)
    if args.search is not None and re.search(re.compile(args.search),string=line) is None:
        return False
    if args.start_date is not None and args.end_date is None:
        if parse_nginx_time_format(parsed_line['time']) < parse_nginx_time_format(args.start_date):
            return False
    if args.end_date is not None and args.start_date is None:
        if parse_nginx_time_format(parsed_line['time']) > parse_nginx_time_format(args.end_date):
            return False
    if args.start_date is not None and args.end_date is not None and (parse_nginx_time_format(parsed_line['time']) > parse_nginx_time_format(args.end_date) or parse_nginx_time_format(parsed_line['time']) < parse_nginx_time_format(args.start_date)):
        return False
    if args.host is not None and parsed_line["host"] != args.host:
        return False
    if args.request is not None and args.request not in parsed_line["request"]:
        return False
    if args.status is not None and parsed_line["status"] != args.status:
        return False
    return True

def parse_line(line):
    fields = line.split(" ")
    return {
            "ip_address":fields[0],
            "time":fields[3].replace("[",""),
            "host":fields[5],
            "request":f'{fields[6]} {fields[7]}',
            "status":fields[9],
            "body_bytes_sent":re.sub("[^\d\.]", "",fields[10]),
            "request_time":re.sub("[^\d\.]", "",fields[15])
            }

def unique_ips_only(lines):
    ip_occurances = {}
    for line in lines:
        ip = line.split(" ")[0]
        if ip not in ip_occurances:
            ip_occurances[ip] = line
    ans = []
    for address,entry in ip_occurances.items():
        ans.append(entry)
    return ans

def parse_nginx_time_format(time):
    return datetime.strptime(time,"%d/%b/%Y:%H:%M:%S")

def generate_analytical_output(log_selection):
    stats = {
            "request_count":0,
            "top_5_requests":{},
            "top_5_hosts":{},
            "average_body_byte_speed":0,
            "average_requests_per_minute":0,
            }
    for line in log_selection:
        parsed_line = parse_line(line)
        stats["request_count"] += 1
        try:
            stats["average_body_byte_speed"] += (float(parsed_line["body_bytes_sent"])/(float(parsed_line["request_time"])+.00001))
        except:
            stats["average_body_byte_speed"] += 0
        if parsed_line["request"] not in stats["top_5_requests"]:
            stats["top_5_requests"][parsed_line["request"]] = {
                    "request_text":parsed_line["request"],
                    "count": 1
                    }
        stats["top_5_requests"][parsed_line["request"]]["count"] += 1

        if parsed_line["host"] not in stats["top_5_hosts"]:
            stats["top_5_hosts"][parsed_line["host"]] = {
                    "host_text":parsed_line["host"],
                    "count": 1
                    }
        stats["top_5_hosts"][parsed_line["host"]]["count"] += 1

    stats["average_body_byte_speed"] = stats["average_body_byte_speed"] / stats["request_count"]
    stats["average_requests_per_minute"] = stats["request_count"]/((parse_nginx_time_format(parse_line(log_selection[-1])["time"]) - parse_nginx_time_format(parse_line(log_selection[0])["time"])).total_seconds()/60)

    new_requests = []
    new_hosts = []
    for request,entry in stats["top_5_requests"].items():
        new_requests.append(entry)

    for host,entry in stats["top_5_hosts"].items():
        new_hosts.append(entry)

    new_hosts.sort(key=lambda x:x != None and x.get("count"),reverse=True)
    new_requests.sort(key=lambda x:x != None and x.get("count"),reverse=True)

    stats["top_5_requests"] = new_requests[:5]
    stats["top_5_hosts"] = new_hosts[:5]

    top_5_hosts_output = ""
    top_5_requests_output = ""
    for item in stats["top_5_hosts"]:
        top_5_hosts_output += f"-{item['host_text']} ~ {format(item['count'],',d')} \n".replace('"','')

    for item in stats["top_5_requests"]:
       top_5_requests_output += f"-{item['request_text']} ~ {format(item['count'],',d')} \n".replace('"','')

    print(f"""
===~ LOG SELECTION STATS ~===
Total Requests: {format (stats['request_count'], ',d')}
Requests Per Min: {round(stats['average_requests_per_minute'],2)}
Average Body Transfer Speed: {round(stats['average_body_byte_speed']/1024/1024,2)} MB/S

Top 5 Requests:
{top_5_requests_output}
Top 5 Hosts:
{top_5_hosts_output}
""")

if __name__ == "__main__":
    with open(f'./{args.file}', 'r') as f:
        final_lines = []
        lines = f.readlines()
        for line in lines:
            if keep_log(line):
                final_lines.append(line)
        if args.unique:
            final_lines = unique_ips_only(final_lines)
        if args.analytics:
            generate_analytical_output(final_lines)
        else:
            for line in final_lines:
                print(line)
