#!/usr/bin/python3

import json
import sqlite3
# from db import *
from globalSettings import *
from global_func import *
import cgi
import cgitb
import os
import filecmp
import shutil
import subprocess
import zipfile
from clearTextPwd import pwd_encoding
cgitb.enable()

conn = sqlite3.connect(DBPath)
conn.text_factory = str
c = conn.cursor()


print("Content-type:/text/json\r\n\r\n")


if (GUI == true):
    form = cgi.FieldStorage()
    request_Type = form.getvalue('RequestType')

def restart():
    try:
        password = pwd_encoding(form.getvalue('password'))
        fw_name = form.getvalue('fw_name')
        c.execute('select * from person where password = ?',[password])
        fw_name = fw_name.split(".zip")[0]
        exists = c.fetchone()
        if exists is not None:
            factory_result = factoryBackup()
            if factory_result["status"] == "fail":
                print(json.JSONEncoder().encode({"status" : "Fail","description" : factory_result["description"]}))
                return
            result = restartGUI(fw_name)
            if result is not True:
                print(json.JSONEncoder().encode({"status" : "Fail","description" : result}))
                return
            c.execute("update versionControl set status = ?",["False"])
            conn.commit()
            c.execute("insert into versionControl (fw_name,status,last_activated,cr_date) values(?,?,datetime(),datetime())",[fw_name,"True"])
            conn.commit()
            print(json.JSONEncoder().encode({"status" : "success"}))
        else:
            print(json.JSONEncoder().encode({"status" : "Fail","description" : "Invalid Password"}))
    except Exception as e:
        print(json.JSONEncoder().encode({"status" : "Fail","description" : str(e)}))
        c.close()
        conn.close()
        return False
    finally:
        c.close()
        conn.close()

def activate():
    try:
        password = pwd_encoding(form.getvalue('password'))
        fw_name = form.getvalue('fw_name')
        c.execute('select * from person where password = ?',[password])
        exists = c.fetchone()
        if exists is not None:
            factory_result = factoryBackup()
            if factory_result["status"] == "fail":
                print(json.JSONEncoder().encode({"status" : "Fail","description" : factory_result["description"]}))
                return
            result = restartGUI(fw_name)
            if result is not True:
                print(json.JSONEncoder().encode({"status" : "Fail","description" : result}))
                return
            c.execute("update versionControl set status = ?",["False"])
            conn.commit()
            c.execute("update versionControl  set status = ?, last_activated = datetime() where fw_name =?",["True",fw_name])
            conn.commit()
            print(json.JSONEncoder().encode({"status" : "success"}))
        else:
            print(json.JSONEncoder().encode({"status" : "Fail","description" : "Invalid Password"}))
    except Exception as e:
        print(json.JSONEncoder().encode({"status" : "Fail","description" : str(e)}))
        return False
    finally:
        c.close()
        conn.close()
def getFw():
    try:
        dirArray = []
        factoryArray = []
        c.execute('select fw_name,status,last_activated,cr_date from versionControl where fw_name is not null ORDER BY cr_date DESC LIMIT 5')
        cols = c.fetchall()
        for item in cols:
            dirArray.append({"fw_name": item[0],"status" :item[1], "last_activated" :item[2], "upload_date" :item[3]})
        
        c.execute('select last_activated,cr_date from versionControl where fw_name is null ORDER BY cr_date DESC')
        cols = c.fetchall()
        for item in cols:
            factoryArray.append({"last_activated" :item[0], "upload_date" :item[1]})
        
        # check factory files exists or not
        factory_path = '/mnt/xdata/factory_files/AdoptionELS.zip'
        if os.path.exists(factory_path):
            factory_files = True
        else:
            factory_files = False
        print(json.JSONEncoder().encode({"dirArray": dirArray, "factory_files" : factory_files, "factory_path" : factory_path, "factoryArray" : factoryArray}))
    except Exception as e:
        print(json.JSONEncoder().encode({"status" : "Fail","description" : str(e)}))
    finally:
        c.close()
        conn.close()


def restartGUI(fw_name):
    try:
        pilotCCMPath = '/home/pilotCCM'
        pilotPath = '/var/www/Adoption-ELS'
        uploadFolderPath = "/mnt/xdata/uploads/"+fw_name
        if not os.path.exists(uploadFolderPath):
            return f"Firmware {fw_name} not found"
        
        folder_to_move_home = os.path.join(uploadFolderPath, 'pilotCCM')
        folder_to_move_var_www = os.path.join(uploadFolderPath, 'Adoption-ELS')

        if not os.path.exists(folder_to_move_home):
            return f"CCM file not found under {fw_name}"

        if not os.path.exists(folder_to_move_var_www):
            return f"Adoption-ELS file not found under {fw_name}"

        replace_changed_files(pilotPath,folder_to_move_var_www)
        replace_changed_files(pilotCCMPath,folder_to_move_home)
        subprocess.Popen(["sudo", "chmod", "-R", "777", pilotPath])
        try:
            subprocess.Popen(
                ["bash", "-c", "sleep 5 && sudo reboot"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL
            )
        except Exception as e:
            pass
        return True
    except Exception as e:
        return str(e)

def replace_changed_files(existing_folder, new_folder):
    for root, dirs, files in os.walk(existing_folder):
        for file in files:
            existing_file_path = os.path.join(root, file)
            new_file_path = os.path.join(new_folder, os.path.relpath(existing_file_path, existing_folder))
            try:
                if os.path.exists(new_file_path):
                    if not filecmp.cmp(existing_file_path, new_file_path, shallow=False):
                        shutil.copy2(new_file_path, existing_file_path)
                else:
                    shutil.copy2(existing_file_path, new_file_path)
            except Exception as e:
                pass
    return True

def deleteFw():
    try:
        fw_name = form.getvalue('fw_name')
        password = pwd_encoding(form.getvalue('password'))
        c.execute('select * from person where password = ?',[password])
        exists = c.fetchone()
        if exists is None:
            print(json.JSONEncoder().encode({"status" : "fail","description" : "Invalid Password"}))
            return
        
        # Delete folders from mnt/xdata/uploads 
        uploadFolderPath = "/mnt/xdata/uploads/"+fw_name
        if os.path.exists(uploadFolderPath):
            subprocess.call(["sudo", "chmod", "-R", "777", uploadFolderPath])
            shutil.rmtree(uploadFolderPath)
        
        c.execute("delete from versionControl where fw_name = ?",[fw_name])
        conn.commit()

        print(json.JSONEncoder().encode({"status" : "success", "description" : "FW Deleted Successfully"}))

    except Exception as e:
        print(json.JSONEncoder().encode({"status" : "fail","description" : str(e)}))
    finally:
        c.close()
        conn.close()

def zip_multiple_folders(zip_path, folders):
    """Zip multiple folders into one zip."""
    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for folder in folders:
            base_name = os.path.basename(folder.rstrip("/"))
            for root, _, files in os.walk(folder):
                for file in files:
                    abs_path = os.path.join(root, file)
                    rel_path = os.path.relpath(abs_path, os.path.dirname(folder))
                    zipf.write(abs_path, rel_path)


def factoryBackup():
    try:
        factory_files = '/mnt/xdata/factory_files'
        pilotCCMPath = '/home/pilotCCM'
        pilotPath = '/var/www/Adoption-ELS'

        # Create folder if missing
        subprocess.check_call(["sudo", "mkdir", "-p", factory_files])
        subprocess.check_call(["sudo", "chmod", "-R", "777", factory_files])

        # ZIP file path
        final_zip = f"{factory_files}/AdoptionELS.zip"

        if os.path.exists(final_zip):
            return {
                "status": "success",
                "description": "Factory backup already exists. Skipping new backup."
            }

        if not os.path.exists(pilotPath):
            return {"status": "fail", "description": "Adoption-ELS folder not found"}
        if not os.path.exists(pilotCCMPath):
            return {"status": "fail", "description": "pilotCCM folder not found"}

        # Prepare temporary copy paths
        tmp_adoption = f"{factory_files}/Adoption-ELS"
        tmp_pilotccm = f"{factory_files}/pilotCCM"

        # Clean temp folders if present
        for f in [tmp_adoption, tmp_pilotccm]:
            if os.path.exists(f):
                shutil.rmtree(f)

        shutil.copytree(pilotPath, tmp_adoption)
        shutil.copytree(pilotCCMPath, tmp_pilotccm)

        zip_multiple_folders(final_zip, [tmp_adoption, tmp_pilotccm])

        shutil.rmtree(tmp_adoption)
        shutil.rmtree(tmp_pilotccm)

        return {
            "status": "success",
            "description": "Factory Backup ZIP Created Successfully"
        }

    except Exception as e:
        return {"status": "fail", "description": str(e)}



def factoryReset():
    try:
        password = pwd_encoding(form.getvalue('password'))
        c.execute('select * from person where password = ?',[password])
        exists = c.fetchone()
        if exists is None:
            return {"status": "fail", "description": "Invalid Password"}
        
        factory_files = '/mnt/xdata/factory_files'
        pilotCCMPath = '/home/pilotCCM'
        pilotPath = '/var/www/Adoption-ELS'

        zip_file = f"{factory_files}/AdoptionELS.zip"
        extract_path = f"{factory_files}/extracted"

        if not os.path.exists(zip_file):
            return {"status": "fail", "description": "Factory file not found"}

        # Clean old extracted folder
        if os.path.exists(extract_path):
            shutil.rmtree(extract_path)

        os.makedirs(extract_path, exist_ok=True)

        # ---- Unzip ----
        with zipfile.ZipFile(zip_file, 'r') as zip_ref:
            zip_ref.extractall(extract_path)

        extracted_adoption = f"{extract_path}/Adoption-ELS"
        extracted_pilotccm = f"{extract_path}/pilotCCM"

        if not os.path.exists(extracted_adoption):
            shutil.rmtree(extract_path)
            return {"status": "fail", "description": "Adoption-ELS not found in ZIP"}

        if not os.path.exists(extracted_pilotccm):
            shutil.rmtree(extract_path)
            return {"status": "fail", "description": "pilotCCM not found in ZIP"}

        # ---- Restore data ----
        replace_changed_files(pilotPath, extracted_adoption)
        replace_changed_files(pilotCCMPath, extracted_pilotccm)

        # ---- Re-create ZIP after restore ----
        zip_multiple_folders(zip_file, [extracted_adoption, extracted_pilotccm])

        # ---- Delete extracted folder after work is complete ----
        if os.path.exists(extract_path):
            shutil.rmtree(extract_path)

        try:
            c.execute("insert into versionControl (last_activated,cr_date) values(datetime(),datetime())")
            conn.commit()
        except Exception as e:
            pass

        # ---- Schedule reboot ----
        try:
            subprocess.Popen(
                ["bash", "-c", "sleep 5 && sudo reboot"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL
            )
        except:
            pass

        return {"status": "success", "description": "Factory Reset Completed Successfully"}

    except Exception as e:
        # Ensure cleanup even on error
        if os.path.exists(extract_path):
            shutil.rmtree(extract_path)

        return {"status": "fail", "description": str(e)}
    finally:
        c.close()
        conn.close()



if request_Type == 'restart':
    restart()
elif request_Type == "activate":
    activate()
elif request_Type == "getFw":
    getFw()
elif request_Type == "delete":
    deleteFw()
elif request_Type == "factoryReset":
    print(json.JSONEncoder().encode(factoryReset()))
else:
    print(json.JSONEncoder().encode({"status" : "Fail","description" : "Invalid Request Type"}))