#!/usr/bin/env python3
# -*- encoding: utf-8; py-indent-offset: 4 -*-
#
# Copyright (C) 2026 comNET GmbH
# <https://www.comnet-solutions.de/>
#
# This is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  This file is distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# ails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.
#


import dataclasses
import json
import logging
import sys
from argparse import ArgumentParser
from collections.abc import Callable
from logging.handlers import RotatingFileHandler
from pathlib import Path
from typing import Any

import requests
from cmk.utils import password_store


logger = logging.getLogger("agent_huawei_pacific.log")
max_bytes = 8388608
format_str = "%(asctime)s, [%(levelname)s][PID:%(process)d]" \
             "[thread:%(thread)d][%(filename)s:%(lineno)d] %(message)s"
date_fmt = "%Y-%m-%d %H:%M:%S.%p"
formatter = logging.Formatter(format_str, date_fmt)

def parse_arguments(argv):
    parser = ArgumentParser(description=__doc__)

    parser.add_argument('-u', '--user', dest='user', required=True,
                        help='User to access the Storage.')
    parser.add_argument('-p', '--password', dest='password',
                        help='Password to access the Storage.', required=True),
    parser.add_argument('--port', required=True,
                        help='Port of the Huawei Storage RESt API. Example '
                             '8088')
    parser.add_argument('--host', required=True,
                        help='Host of the Huawei Storage RESt API.')
    parser.add_argument('--ssl-verify', dest='ssl_verify', action='store_true',
                        help='Do not verify the SSL cert from the REST.')
    parser.add_argument('--log-path', dest='log_path', help='Agent log path')
    parser.add_argument('--sections', dest='sections',
                        required=True,
                        help='Comma separated list of data to query')
    parser.add_argument('--debug-output', dest='debug_output',
                        action='store_true',
                        help='Debug Output:'
                             ' - Get the raw API json output for important endpoints'
                             ' - Get Info which queries fail and why')

    return parser.parse_args(argv)

FetchFunction = Callable[[str], list]

@dataclasses.dataclass
class SectionFetchInfo:
    endpoint: str
    fetch_function: FetchFunction | None = None # if None, the agent default section data fetch function is used
    data_key: str | None = "data"

# agent
class AgentHuaweiPacific:
    def __init__(self, args):
        self._req_session = requests.Session()
        self.base_url = f"https://{args.host}:{args.port}"
        self.login_url = self.base_url + "/api/v2/aa/sessions"

        self.user = args.user
        self.password = args.password
        self.ssl_verify = args.ssl_verify

        self.all_luns: dict = {}
        self.all_namespaces: dict = {}
        self.management_internal_ip_address: str = ""

        self.wanted_sections = args.sections.split(",") if args.sections else []
        self.section_fetch_info: dict[str, SectionFetchInfo] = {
            "storagepool": SectionFetchInfo(
                endpoint="/api/v2/data_service/storagepool",
                data_key="storagePools"
            ),
            "namespace": SectionFetchInfo(
                endpoint="/api/v2/converged_service/namespaces"
            ),
            "nodes": SectionFetchInfo(
                endpoint="/api/v2/cluster/servers"
            ),
            "diskpool": SectionFetchInfo(
                endpoint="/api/v2/data_service/diskpool",
                data_key="diskPools"
            ),
            "alarms": SectionFetchInfo(
                endpoint="/api/v2/common/events",
            ),
            "replication_pair": SectionFetchInfo(
                endpoint="/dsware/service/v1.3/REPLICATIONPAIR",
            ),
            "dtrees": SectionFetchInfo(
                endpoint="/api/v2/file_service/dtrees",
                fetch_function=self._dtrees_section_fetch_function
            ),
            "smart_tier": SectionFetchInfo(
                endpoint="/api/v2/tier_service/tier_placement_policies"
            ),
            "ports": SectionFetchInfo(
                endpoint="/api/v2/network_service/servers",
                fetch_function=self._ports_section_fetch_function
            ),
            "control_cluster": SectionFetchInfo(
                endpoint="/dsware/service/cluster/queryManageCluster",
                data_key=None
            ),
        }
        self.fetched_section_data: dict[str, Any] = {}

    def get_all_namespaces(self) -> None:
        url = f"{self.base_url}/api/v2/converged_service/namespaces"
        try:
            response = self._req_session.get(url)
            raw_data = response.json()["data"]
            for ns_info in raw_data:
                self.all_namespaces[ns_info["id"]] = ns_info
        except (KeyError, json.JSONDecodeError, requests.RequestException) as e:
            logger.error(f"Failed to get all namespaces for dtrees: {e}")

    def get_management_internal_ip_address(self):
        url = f"{self.base_url}/api/v2/network_service/obs_service_nodes"
        try:
            response = self._req_session.post(url)
            self.management_internal_ip_address = response.json()["management_internal_ip"]
        except (KeyError, json.JSONDecodeError, requests.RequestException) as e:
            logger.error(f"Failed to fetch management internal IP address: {e}")

    def initialize_agent_data(self) -> None:
        self.get_all_namespaces()

    def _ports_section_fetch_function(self, url: str):
        response = self._req_session.post(url, params={
            "management_internal_ip": self.management_internal_ip_address
        })
        return response.json()["data"]["physical_ports"]

    def _dtrees_section_fetch_function(self, url: str):
        all_dtrees_data = []
        for ns_id in self.all_namespaces:
            try:
                response = self._req_session.get(url, params={
                    "file_system_id": ns_id
                })
                all_dtrees_data.extend(response.json()["data"])
            except (KeyError, json.JSONDecodeError, requests.RequestException) as e:
                logger.error(f"Failed to fetch dtrees for file system with id {ns_id}: {e}")
        return all_dtrees_data

    def _make_secret(self) -> str:
        if (ref := self.password) is None:
            raise Exception("No password was given")
        pw_id, pw_file = ref.split(":", 1)
        return password_store.lookup(Path(pw_file), pw_id)

    def login(self) -> None:
        login_data = {
            'user_name': self.user,
            'password': self._make_secret(),
        }
        try:
            login_data = self._req_session.post(self.login_url,
                                               json=login_data,
                                               verify=self.ssl_verify,
                                               timeout=60)
            self._req_session.headers.update({
                "X-Auth-Token": login_data.json()["data"]["x_auth_token"]
            })
        except requests.exceptions.RequestException as e:
            logger.error(f"Could not login.", exc_info=e)
            return
        except KeyError as e:
            logger.error(f"Could not login.", exc_info=e)
            return

        self.initialize_agent_data()

    def logout(self) -> None:
        self._req_session.delete(self.login_url, timeout=60)
        self._req_session.close()
        self._req_session = None

    def get_section_data(self, section_info: SectionFetchInfo) -> list | dict | None:
        url = f"{self.base_url}{section_info.endpoint}"
        if not section_info.fetch_function:
            result = self._req_session.get(url)
            result = result.json() if result else {}
        else:
            result = section_info.fetch_function(url)
        if isinstance(result, list):
            return result
        if not section_info.data_key:
            return result
        return result[section_info.data_key]

    def collect_section_data(self) -> None:
        for section_name in self.wanted_sections:
            try:
                section_info = self.section_fetch_info.get(section_name)
                self.fetched_section_data[section_name] = self.get_section_data(section_info)
            except (ValueError, KeyError, requests.RequestException, json.JSONDecodeError) as e:
                logger.error(f"Could not collect section data for section '{section_name}'.", exc_info=e)
                continue

    def print_section(self, section_name: str):
        section_title = f"<<<huawei_pacific_{section_name.lower()}:sep(0)>>>"
        print(section_title)
        section_data = self.fetched_section_data.get(section_name)
        if isinstance(section_data, list):
            for data_dict in section_data:
                print(json.dumps(data_dict))
            return
        if isinstance(section_data, dict):
            print(json.dumps(section_data))
            return


    def print_sections(self) -> None:
        for section_name in self.wanted_sections:
            self.print_section(section_name)

    def sections_debug_output(self):
        seperator = "-----------------"
        print(seperator)
        print(seperator)
        print(seperator)
        print(f"[DEBUG OUTPUT]")
        print(f"[[RAW JSON ENDPOINT OUTPUT]]")
        print(seperator)
        print(seperator)
        print(seperator)
        for endpoint_name, url in [
            ("get_management_internal_ip_address", f"{self.base_url}/api/v2/network_service/obs_service_nodes"),
            ("get_all_namespaces", f"{self.base_url}/api/v2/converged_service/namespaces"),
        ]:
            print(f"[[[{endpoint_name}:{url}]]]")
            try:
                response = self._req_session.get(url)
                if hasattr(response, "json"):
                    print(json.dumps(response.json()))
                else:
                    if type(response) is dict or type(response) is list:
                        print(response)
                        continue
                    print(response.text)
            except requests.RequestException as e:
                print(f"[[[REQUEST ERROR: {endpoint_name}({url})]]]")
                print(f"{e}")
                print(seperator)
            except (KeyError, ValueError, json.JSONDecodeError) as e:
                print(f"[[[JSON ERROR: {endpoint_name}({url})]]]")
                print(f"{e}")
                print(seperator)

        print(seperator)
        print("All luns: ", self.all_luns)
        print("All namespaces: ", self.all_namespaces)
        print("Management internal IP Adrress: ", self.management_internal_ip_address)
        print(seperator)
        print(seperator)

        for section_name in self.wanted_sections:
            section_info = self.section_fetch_info.get(section_name)
            url = f"{self.base_url}{section_info.endpoint}"
            try:
                if not section_info.fetch_function:
                    response = self._req_session.get(url)
                else:
                    response = section_info.fetch_function(url)
                print(f"[[[{section_name}]]]")
                if hasattr(response, "json"):
                    print(json.dumps(response.json()))
                else:
                    if type(response) is dict or type(response) is list:
                        print(response)
                        continue
                    print(response.text)
                print(seperator)
            except requests.RequestException as e:
                print(f"[[[REQUEST ERROR: {section_name}]]]")
                print(f"{e}")
                print(seperator)
            except (KeyError, ValueError, json.JSONDecodeError) as e:
                print(f"[[[JSON ERROR: {section_name}]]]")
                print(f"{e}")
                print(seperator)

if __name__ == '__main__':
    args = parse_arguments(sys.argv[1:])

    handler = RotatingFileHandler(filename=args.log_path, maxBytes=max_bytes)
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    agent =  AgentHuaweiPacific(args)
    agent.login()
    if args.debug_output:
        agent.sections_debug_output()
    else:
        agent.collect_section_data()
        agent.print_sections()
    agent.logout()
