import base64
import json
import subprocess

from jsonschema import SchemaError, ValidationError, validate

from app.api.utils import Service
from app.config import ALERTS_DIR, MAIN_SCRIPT, SCRIPTS_SCHEMA_PATH
from app.utils import Result, load_schema
from app.utils.check_processing import ScriptStatus


def decode_base64_encoded_keys(key, val) -> str:
    base64_encoded_keys = [
        "backup_status",
        "big_dirs",
        "big_files",
        "file_type",
        "named_rebuild_result",
    ]
    for encoded_key in base64_encoded_keys:
        if encoded_key in key:
            base64_bytes = val.encode("utf-8")
            string_bytes = base64.b64decode(base64_bytes)
            decoded_payload = string_bytes.decode("utf-8")
            return decoded_payload

    return val


def script_not_started(service: Service) -> bool:
    no_file = ": cannot open"

    cmd = f"file {service.background_data}"
    cmd_result = subprocess.Popen(
        [cmd],
        shell=True,
        stdout=subprocess.PIPE,
        universal_newlines=True,
    ).communicate()[0]
    # If the background data file is missing, then the bash script was not started to run in the background earlier and
    # can be started now.
    if no_file in cmd_result:
        return True

    return False


def sort_data(cmd_result: str) -> dict:
    top_lvl_vars = ["auto_fix_status", "script_status", "force_report"]
    raw_data = json.loads(cmd_result)
    sorted_data = {"payload": {}}
    for k in raw_data.keys():
        if k in top_lvl_vars:
            sorted_data[k] = raw_data[k]
        else:
            sorted_data["payload"][k] = decode_base64_encoded_keys(k, raw_data[k])

    return sorted_data


def run_service_check(service: Service) -> Result:
    service_check_result = Result()

    try:
        cmd = f"source {ALERTS_DIR}/{MAIN_SCRIPT}; {service.auto_fix_script}"
        if script_not_started(service):
            # start_script
            subprocess.Popen(
                [cmd],
                shell=True,
                stdout=subprocess.DEVNULL,
                close_fds=True,
            )
            service_check_result.data = {"script_status": ScriptStatus.STARTED.codename}
        else:
            # check script progress or result
            cmd_result = subprocess.Popen(
                [cmd],
                shell=True,
                stdout=subprocess.PIPE,
            ).communicate()[0]

            prepared_payload = sort_data(cmd_result)
            validate(instance=prepared_payload, schema=load_schema(SCRIPTS_SCHEMA_PATH))
            service_check_result.data = prepared_payload

    except SchemaError as err:
        service_check_result.success = False
        service_check_result.message = err.message or repr(err)
    except ValidationError as err:
        service_check_result.success = False
        service_check_result.message = (
            f"Script output is invalid: {err.message} in\n{json.dumps(prepared_payload, indent=2)}\n"
            if err.message
            else repr(err)
        )
    except Exception as err:
        service_check_result.success = False
        service_check_result.message = repr(err)

    return service_check_result
