import os
import json
import yaml
from datetime import datetime, date
from typing import Optional, Any, Union

from .fsutils import check_folder_exist
from .stringutils import varName
from .dateutils import parse_date


class BaseMl:
    def __init__(self, fname: Optional[str] = None, encoding: str = 'utf-8', return_: Any = None):
        if fname:
            file_dir, file_name = os.path.split(fname)
        else:
            file_dir, file_name = None, None
        self.fname = file_name
        self.fpath = file_dir
        self.encoding = encoding
        self.return_ = return_
        self.data = None

    def reinit(self, fname: Optional[str] = None, encoding: str = 'utf-8', return_: Any = None):
        if fname:
            file_dir, file_name = os.path.split(fname)
        else:
            file_dir, file_name = None, None
        self.fname = file_name
        self.fpath = file_dir
        self.encoding = encoding
        self.return_ = return_

    def set_data(self, data: Any):
        self.data = data

    def get_data(self):
        return self.data


class Yaml(BaseMl):
    def parse(self):
        try:
            with open(os.path.join(self.fpath, self.fname), 'r', encoding=self.encoding) as yaml_file:
                self.data = yaml.full_load(yaml_file)
        except Exception:
            self.data = self.return_
        return self.data

    def create(self):
        if self.fname:
            check_folder_exist(self.fpath)
            with open(os.path.join(self.fpath, self.fname), 'w', encoding=self.encoding) as outfile:
                yaml.dump(self.data, outfile, default_flow_style=False, indent=4,
                          allow_unicode=True, sort_keys=False)
        else:
            raise ValueError('Please provide filename first!')


class Json(BaseMl):
    def parse(self):
        try:
            with open(os.path.join(self.fpath, self.fname), 'r', encoding=self.encoding) as json_file:
                json_str = json_file.read()
                self.data = json.loads(json_str, object_hook=self.date_hook)
        except Exception:
            self.data = self.return_
        return self.data

    def create(self):
        if self.fname:
            check_folder_exist(self.fpath)
            dthandler = lambda obj: obj.isoformat() if isinstance(obj, (datetime, date)) else None
            with open(os.path.join(self.fpath, self.fname), 'w', encoding=self.encoding) as outfile:
                json.dump(self.data, outfile, ensure_ascii=False, default=dthandler, indent=4)
        else:
            raise ValueError('Please provide filename first!')

    def dumps(self):
        dthandler = lambda obj: obj.isoformat() if isinstance(obj, (datetime, date)) else None
        return json.dumps(self.data, ensure_ascii=False, default=dthandler, indent=4)

    @staticmethod
    def date_hook(json_dict: dict):
        for (key, value) in json_dict.items():
            date_value = parse_date(value)
            if isinstance(date_value, (date, datetime)):
                json_dict[key] = date_value
        return json_dict


def create_json(json_data: Any, json_fname: str = None, encoding: str = 'utf-8'):
    if not json_fname:
        json_fname = f'{varName(json_data)}.json'
    j = Json(json_fname, encoding)
    j.set_data(json_data)
    j.create()


def parse_json(json_fname: str, encoding: str = 'utf-8', return_: Any = None):
    j = Json(json_fname, encoding, return_)
    return j.parse()


def create_yaml(yaml_data: Any, yaml_fname: str = None, encoding: str = 'utf-8'):
    if not yaml_fname:
        yaml_fname = f'{varName(yaml_data)}.yml'
    y = Yaml(yaml_fname, encoding)
    y.set_data(yaml_data)
    y.create()


def parse_yaml(yaml_fname: str, encoding: str = 'utf-8', return_: Any = None):
    y = Yaml(yaml_fname, encoding, return_)
    return y.parse()
