#!/usr/bin/env python3
import json
import argparse
import re
import traceback
import sys
import os
import pathlib
import ipaddress

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from config import cfg as _cfg
_paths = _cfg.paths

CUR_PATH, _ = os.path.split(os.path.abspath(__file__))

sys.path.append(str(pathlib.Path(CUR_PATH).parent))

from logic.storage_operate import StorageInf
from utils.client.rest_client import read_helper, write_helper
from utils.client.response_parse import ResponseParse
from om_log import REST_LOG as LOG

FS_PARAM_PATH = str(pathlib.Path(CUR_PATH, "../../config/file_system_info.json"))
DEPLOY_PARAM_PATH = str(pathlib.Path(CUR_PATH, "../../config/deploy_param.json"))
DEPLOY_PARAM = json.loads(read_helper(DEPLOY_PARAM_PATH))
DEPLOY_MODE = DEPLOY_PARAM.get("deploy_mode")
FS_TYPE_LIST = [
    "storage_dbstor_fs", "storage_dbstor_page_fs",
    "storage_share_fs", "storage_archive_fs",
    "storage_metadata_fs"
]
if DEPLOY_MODE == "dbstor":
    FS_TYPE_LIST = [
        "storage_dbstor_fs", "storage_dbstor_page_fs",
        "storage_share_fs", "storage_archive_fs"
    ]
SHARE_FS_TYPE_LIST = [
    "storage_share_fs", "storage_archive_fs",
    "storage_metadata_fs"
]
if DEPLOY_MODE == "file":
    FS_TYPE_LIST = [
        "storage_dbstor_fs","storage_share_fs",
        "storage_archive_fs", "storage_metadata_fs"
    ]
    SHARE_FS_TYPE_LIST = [
        "storage_dbstor_fs", "storage_share_fs",
        "storage_archive_fs", "storage_metadata_fs"
    ]
ID_NAS_DBSTOR = 1038
ID_NAS_DEFAULT = 11


def is_valid_string(string):
    """Validate filesystem name: alphanumeric, underscore, Chinese chars, '.', '-', length 1-255."""
    pattern = r'^[\w\u4e00-\u9fa5.-]{1,255}$'
    match = re.match(pattern, string)
    return match is not None


def is_valid_ip_range(ip_range):
    if ip_range == "*":
        return True
    try:
        ipaddress.IPv4Network(ip_range, strict=False)
        return True
    except ipaddress.AddressValueError:
        return False


class CreateFS(object):
    def __init__(self, login_tuple):
        self.ip_addr, self.user_name, self.passwd = login_tuple
        self.storage_opt = StorageInf(login_tuple)
        self.fs_info = self._init_params()
        self.pre_check_result = list()
        self.deploy_info = DEPLOY_PARAM

    @staticmethod
    def handle_error_msg(err_msg):
        LOG.error(err_msg)
        raise Exception(err_msg)

    @staticmethod
    def check_capacity(capacity):
        """Check if capacity format is valid (e.g. 300GB/30TB/3PB)."""
        if capacity.endswith(("GB", "TB", "PB")):
            capacity_digit = re.findall(r"\d+", capacity)
            if capacity_digit:
                return capacity_digit[0].isnumeric()
        return False

    @staticmethod
    def compute_capacity(capacity):
        """Convert capacity (GB/TB/PB) to sectors (512 bytes each)."""
        capacity_digit = re.findall(r"\d+", capacity)[0]
        capacity_unit = re.findall(r"[A-Z]+", capacity)[0]
        convert_dict = {
            "GB": 1,
            "TB": 1000,
            "PB": 1000 ** 2
        }
        return convert_dict.get(capacity_unit) * int(capacity_digit) * 1024 * 1024 * 2

    @staticmethod
    def _init_params():
        fs_info = json.loads(read_helper(FS_PARAM_PATH))
        deploy_info = json.loads(read_helper(DEPLOY_PARAM_PATH))
        for fs_type in FS_TYPE_LIST:
            fs_info[fs_type]["NAME"] = deploy_info.get(fs_type)
        return fs_info

    @classmethod
    def _result_parse(cls, err_msg, res):
        result = ResponseParse(res)
        rsp_code, rsp_result, rsp_data = result.get_rsp_data()
        error_code = rsp_result.get('code')
        if rsp_code != 0 or error_code != 0:
            error_des = rsp_result.get('description')
            error_sgt = rsp_result.get('suggestion')
            err_msg = err_msg % (error_code, error_des, error_sgt)
            cls.handle_error_msg(err_msg)
        return rsp_data

    def pre_check(self):
        """
        check fs info before create
        step:
            1. check param type correct
            2. check user_name, pass_wd correct
            3. check fs exists
            4. check vstore exists
        :return:
        """
        def _check_func():
            pool_id = self.fs_info.get("PARENTID")
            self._check_vstore_exists()
            self._check_storage_pool_exists(pool_id)
            self._check_fs_exists()
        LOG.info("Check file system params start.")
        self._check_param_type()
        if self.deploy_info.get("deploy_mode") == "dbstor":
            self._check_vstore_id()
        self.storage_opt.login()
        try:
            _check_func()
        finally:
            self.storage_opt.logout()
        LOG.info("Check file system params success.")

    def create(self):
        """
        auto create file system.
        step:
            1. query the file system name from deploy_param.json
            2. query fs info from DM; if exists, return ERROR
            3. create fs by OM restfull interface, record fs info
            4. create nfs share, recorde nfs share info
            5. add nfs client
        :return:
        """
        def _create_func():
            fs_info = {}
            deploy_mode = self.deploy_info.get("deploy_mode")
            for fs_type in FS_TYPE_LIST:
                nfs_share_id = None
                nfs_share_client_id = None
                _fs_info = self.fs_info.get(fs_type)
                _fs_name = _fs_info.get("NAME")
                LOG.info("Begin to create fs [%s] name: %s", fs_type, _fs_name)
                vstore_id = self.fs_info.get(fs_type).get("vstoreId")
                LOG.info("Begin to create fs [%s] name: %s, vstore id:[%s] in [%s] deploy mode",
                         fs_type, _fs_name, vstore_id, deploy_mode)
                _fs_info = self.storage_opt.query_filesystem_info(_fs_name, vstore_id)
                if _fs_info:
                    err_msg = "The file system[%s] already exists." % _fs_name
                    self.handle_error_msg(err_msg)
                if fs_type in SHARE_FS_TYPE_LIST and deploy_mode != "dbstor":
                    fs_id = self._create_fs(fs_type, work_load_type=ID_NAS_DEFAULT)
                    nfs_share_id = self._create_nfs_share(fs_id, fs_type)
                    nfs_share_client_id = self._add_nfs_client(nfs_share_id, fs_type)
                else:
                    fs_id = self._create_fs(fs_type, work_load_type=ID_NAS_DBSTOR)
                fs_info[fs_type] = {
                    "fs_id": fs_id,
                    "nfs_share_id": nfs_share_id,
                    "nfs_share_client_id": nfs_share_client_id
                }
                LOG.info("Create fs [%s] success, detail:name[%s], info:%s", fs_type, _fs_name, fs_info)

        LOG.info("Create fs start.")
        self.storage_opt.login()
        try:
            _create_func()
        finally:
            self.storage_opt.logout()
        LOG.info("Create fs end.")

    def delete(self):
        """
        auto delete fs info
        step:
            1. query the file system name from deploy_param.json
            2. query fs info from DM, if not exists, break
            3. query nfs share info, if not exists, continue, else del
            4. delete fs info
        :return:
        """
        def _delete_func():
            for fs_type in FS_TYPE_LIST:
                _fs_info = self.fs_info.get(fs_type)
                _fs_name = _fs_info.get("NAME")
                LOG.info("Begin to del fs [%s] name: %s", fs_type, _fs_name)
                vstore_id = self.fs_info.get(fs_type).get("vstoreId")
                fs_info = self.storage_opt.query_filesystem_info(_fs_name, vstore_id)
                if not fs_info:
                    LOG.info("fs [%s] name %s is not exist", fs_type, _fs_name)
                    continue
                fs_id = fs_info.get("ID")
                nfs_share_info = self.storage_opt.query_nfs_info(fs_id, vstore_id)
                if nfs_share_info:
                    nfs_share_id = nfs_share_info[0].get("ID")
                    self.storage_opt.delete_nfs_share(nfs_share_id, vstore_id)
                else:
                    LOG.info("The nfs share of fs [%s] name %s is not exist", fs_type, _fs_name)
                self.storage_opt.delete_file_system(fs_id)
        LOG.info("Delete fs info start.")
        self.storage_opt.login()
        try:
            _delete_func()
        finally:
            self.storage_opt.logout()
        LOG.info("Delete fs info end.")

    def _check_vstore_id(self):
        deploy_info = json.loads(read_helper(DEPLOY_PARAM_PATH))
        deploy_info_dbstor_fs_vstore_id = deploy_info.get("dbstor_fs_vstore_id")
        fs_info_dbstor_fs_vstore_id = self.fs_info.get("storage_dbstor_fs").get("vstoreId")
        if int(deploy_info_dbstor_fs_vstore_id) != int(fs_info_dbstor_fs_vstore_id):
            err_msg = "dbstor_fs_vstore_id  of config_params.json is " \
                      "different from file_system_info.json,details:" \
                      " dbstor_fs_vstore_id:(%s, %s)" % (fs_info_dbstor_fs_vstore_id,
                                                          deploy_info_dbstor_fs_vstore_id)
            LOG.error(err_msg)
            raise Exception(err_msg)

    def _check_param_type(self):
        """
        check create fs params type
        PARENTID: storage pool id, type: int
        SNAPSHOTRESERVEPER: snapshot reserve percentage, type: int, range: 0-50
        CAPACITYTHRESHOLD: total space alarm threshold, type: int, range: 50-99
        CAPACITY: file system capacity, type: int
        vstoreId: vstore id, type: int
        :return:bool
        """
        err_dict = {}
        name_err = []
        digit_err = []
        range_err = []
        range_check = {
            "SNAPSHOTRESERVEPER": (0, 51),
            "CAPACITYTHRESHOLD": (50, 99)
        }
        digit_check = ["SNAPSHOTRESERVEPER", "CAPACITYTHRESHOLD"]
        pool_id = self.fs_info.get("PARENTID")
        if not isinstance(pool_id, int):
            digit_err.append("PARENTID")
        client_ip = self.fs_info.get("client_ip")
        if not is_valid_ip_range(client_ip):
            range_err.append({"client_ip": client_ip})
        for fs_type in FS_TYPE_LIST:
            fs_name = self.fs_info.get(fs_type).get("NAME")
            fs_info = self.fs_info.get(fs_type)
            vstore_id = self.fs_info.get(fs_type).get("vstoreId")
            if not isinstance(vstore_id, int):
                digit_err.append({fs_type: "vstoreId"})
            if not is_valid_string(fs_name):
                name_err.append({fs_type: fs_name})
            for key, value in fs_info.items():
                if key in digit_check and not isinstance(value, int):
                    digit_err.append({fs_type: key})
                if key in list(range_check.keys()) and value not in range(*range_check.get(key)):
                    range_err.append({fs_type: key})
                if key == "CAPACITY" and not self.check_capacity(value):
                    digit_err.append({fs_type: key})
        if name_err:
            err_dict["name_err"] = name_err
        if digit_err:
            err_dict["type_err"] = digit_err
        if range_err:
            err_dict["range_err"] = range_err
        if err_dict:
            err_msg = "Before create fs check error, details:%s" % err_dict
            self.handle_error_msg(err_msg)

    def _check_fs_exists(self):
        """
        check fs exists before create
        :return: None
        """
        check_fail = []
        for fs_type in FS_TYPE_LIST:
            _fs_name = self.fs_info.get(fs_type).get("NAME")
            vstore_id = self.fs_info.get(fs_type).get("vstoreId")
            _fs_info = self.storage_opt.query_filesystem_info(_fs_name, vstore_id)
            if _fs_info:
                check_fail.append(_fs_name)
        if check_fail:
            err_msg = "File system%s exists" % check_fail
            self.handle_error_msg(err_msg)

    def _check_storage_pool_exists(self, pool_id):
        """
        check storage pool id exists
        :param pool_id: pool id
        :return: None
        """
        resp_data = self.storage_opt.query_storage_pool_info(pool_id)
        if not resp_data:
            err_msg = "Pool id[%s] not exists" % pool_id
            self.handle_error_msg(err_msg)

    def _check_vstore_exists(self):
        """
        check vstore id exists
        :return: bool
        """
        check_fail = []
        for fs_type in FS_TYPE_LIST:
            _fs_name = self.fs_info.get(fs_type).get("NAME")
            vstore_id = self.fs_info.get(fs_type).get("vstoreId")
            try:
                self.storage_opt.query_vstore_info(vstore_id)
            except Exception as _err:
                err_msg = "Vstore id[%s] not exists" % vstore_id
                check_fail.append(err_msg)
        if check_fail:
            err_msg = "Check vstore failed: %s" % check_fail
            self.handle_error_msg(err_msg)

    def _get_fs_info(self, fs_type, work_load_type):
        data = {
            "PARENTID": self.fs_info.get("PARENTID"),
            "workloadTypeId": work_load_type,
        }
        capacity = self.compute_capacity(self.fs_info[fs_type].get("CAPACITY"))
        data.update(self.fs_info.get(fs_type))
        data["CAPACITY"] = capacity
        return data

    def _get_nfs_share_info(self, fs_type, fs_id):
        data = {
            "SHAREPATH": f"/{ self.fs_info.get(fs_type).get('NAME')}/",
            "vstoreId":  self.fs_info.get(fs_type).get("vstoreId"),
            "FSID": fs_id
        }
        data.update(self.fs_info.get(fs_type))
        return data

    def _get_share_client_info(self, parent_id, fs_type):
        data = {
            "ACCESSVAL": 1,
            "ALLSQUASH": 1,
            "ROOTSQUASH": 1,
            "PARENTID": parent_id,
            "vstoreId": self.fs_info.get(fs_type).get("vstoreId"),
            "NAME": self.fs_info.get("client_ip")
        }
        return data

    def _recorde_fs_info(self, fs_info):
        recorde_info = self.fs_info
        for fs_type in FS_TYPE_LIST:
            recorde_info.get(fs_type).update(fs_info.get(fs_type))
        write_helper(FS_PARAM_PATH, recorde_info)

    def _create_fs(self, fs_type, work_load_type):
        """
        param fs_type: storage_dbstor_fs、storage_share_fs、storage_archive_fs、storage_metadata_fs
        :return:the file system id
        """
        data = self._get_fs_info(fs_type, work_load_type)
        return self.storage_opt.create_file_system(data)

    def _create_nfs_share(self, fs_id, fs_type):
        """
        add nfs share
        :param fs_id: the file system id
        :param fs_type: storage_dbstor_fs、storage_share_fs、storage_archive_fs、storage_metadata_fs
        :return: nfs share id
        """
        data = self._get_nfs_share_info(fs_type, fs_id)
        return self.storage_opt.create_nfs_share(data)

    def _add_nfs_client(self, nfs_share_id, fs_type):
        """
        add nfs client
        :param nfs_share_id: NFS share ID
        :return: nfs client id
        """
        data = self._get_share_client_info(nfs_share_id, fs_type)
        return self.storage_opt.add_nfs_client(data)

def main():
    create_parser = argparse.ArgumentParser()
    create_parser.add_argument('--action', choices=["create", "delete", "pre_check"], dest="action", required=True)
    create_parser.add_argument('--ip', dest="ip_addr", required=True)
    args = create_parser.parse_args()
    action = args.action
    ip_addr = args.ip_addr
    user_name = input()
    passwd = input()
    login_param = (ip_addr, user_name, passwd)
    create_fs = CreateFS(login_param)
    getattr(create_fs, action)()


if __name__ == "__main__":
    try:
        main()
    except Exception as err:
        LOG.error("Execute create fs failed, details:%s, traceback:%s", str(err), traceback.format_exc())
        exit(1)