#!/usr/bin/python
# -*- coding: utf-8 -*-
# -----------------------------------------
# Superna App Connector (SOAR)
# - API key + base_url only
# - Allows self-signed certs by default (verify_server_cert default False)
# - Handles text/plain healthcheck responses as SUCCESS
# -----------------------------------------

import json
import requests

import phantom.app as phantom
from phantom.action_result import ActionResult
from phantom.base_connector import BaseConnector


class RetVal(tuple):
    def __new__(cls, val1, val2=None):
        return tuple.__new__(RetVal, (val1, val2))


class SupernaConnector(BaseConnector):

    def __init__(self):
        super(SupernaConnector, self).__init__()
        self._state = None
        self._base_url = None

    def _process_empty_response(self, response, action_result):
        if response.status_code == 200:
            return RetVal(phantom.APP_SUCCESS, {})
        return RetVal(
            action_result.set_status(
                phantom.APP_ERROR, "Empty response and no information in the header"
            ),
            None
        )

    def _process_html_response(self, response, action_result):
        # HTML usually means a proxy / gateway error page.
        status_code = response.status_code
        error_text = response.text or ""

        # Optional: try BeautifulSoup if available (do NOT hard-require bs4)
        try:
            from bs4 import BeautifulSoup  # noqa: F401
            soup = BeautifulSoup(error_text, "html.parser")
            error_text = soup.get_text("\n")
        except Exception:
            # Fall back to raw HTML text
            pass

        # Clean it up a bit
        split_lines = [x.strip() for x in error_text.splitlines() if x.strip()]
        error_text = "\n".join(split_lines[:200])  # cap to keep logs readable

        message = "Status Code: {0}. Data from server:\n{1}\n".format(status_code, error_text)
        message = message.replace(u'{', '{{').replace(u'}', '}}')
        return RetVal(action_result.set_status(phantom.APP_ERROR, message), None)

    def _process_json_response(self, r, action_result):
        try:
            resp_json = r.json()
        except Exception as e:
            return RetVal(
                action_result.set_status(
                    phantom.APP_ERROR,
                    "Unable to parse JSON response. Error: {0}".format(str(e))
                ),
                None
            )

        if 200 <= r.status_code < 399:
            return RetVal(phantom.APP_SUCCESS, resp_json)

        message = "Error from server. Status Code: {0} Data from server: {1}".format(
            r.status_code,
            (r.text or "").replace(u'{', '{{').replace(u'}', '}}')
        )
        return RetVal(action_result.set_status(phantom.APP_ERROR, message), None)

    def _process_response(self, r, action_result):
        # store response in debug data so it shows up in logs on failure
        if hasattr(action_result, 'add_debug_data'):
            action_result.add_debug_data({'r_status_code': r.status_code})
            action_result.add_debug_data({'r_text': r.text})
            action_result.add_debug_data({'r_headers': dict(r.headers)})

        content_type = (r.headers.get('Content-Type', '') or '').lower()

        # IMPORTANT: healthcheck returns text/plain; treat 2xx text/* as success
        if 200 <= r.status_code < 300 and content_type.startswith('text/'):
            return RetVal(phantom.APP_SUCCESS, {"response": r.text})

        # JSON
        if 'json' in content_type:
            return self._process_json_response(r, action_result)

        # HTML error page (often proxy)
        if 'html' in content_type:
            return self._process_html_response(r, action_result)

        # Empty response
        if not r.text:
            return self._process_empty_response(r, action_result)

        # Unknown content-type with body -> error
        message = "Can't process response from server. Status Code: {0} Data from server: {1}".format(
            r.status_code,
            (r.text or "").replace('{', '{{').replace('}', '}}')
        )
        return RetVal(action_result.set_status(phantom.APP_ERROR, message), None)

    def _make_rest_call(self, endpoint, action_result, method="get", **kwargs):
        config = self.get_config()
        resp_json = None

        try:
            request_func = getattr(requests, method)
        except AttributeError:
            return RetVal(
                action_result.set_status(phantom.APP_ERROR, "Invalid method: {0}".format(method)),
                resp_json
            )

        if not self._base_url:
            return RetVal(
                action_result.set_status(phantom.APP_ERROR, "Missing 'base_url' in asset configuration"),
                resp_json
            )

        # Normalize base_url + endpoint
        base = self._base_url.rstrip('/')
        ep = endpoint if endpoint.startswith('/') else '/' + endpoint
        url = base + ep

        try:
            r = request_func(
                url,
                # Allow self-signed by default:
                # verify_server_cert=False -> verify=False (self-signed allowed)
                # verify_server_cert=True  -> verify=True  (strict verification)
                verify=config.get('verify_server_cert', False),
                timeout=60,
                **kwargs
            )
        except Exception as e:
            return RetVal(
                action_result.set_status(
                    phantom.APP_ERROR, "Error Connecting to server. Details: {0}".format(str(e))
                ),
                resp_json
            )

        return self._process_response(r, action_result)

    def _handle_create_data_snapshots(self, param):
        action_result = self.add_action_result(ActionResult(dict(param)))

        config = self.get_config()
        api_key = config.get("api_key")
        if not api_key:
            return action_result.set_status(phantom.APP_ERROR, "Missing 'api_key' in asset configuration")

        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "api_key": api_key
        }

        self.save_progress("Requesting data snapshot from IGLS (critical paths)")

        ret_val, resp_json = self._make_rest_call(
            endpoint="/sera/v2/ransomware/criticalpaths",
            action_result=action_result,
            method="post",
            headers=headers,
            json={}
        )

        if phantom.is_fail(ret_val):
            return action_result.get_status()

        # Store response in action_result data
        count = 0
        if isinstance(resp_json, list):
            action_result.add_data({"items": resp_json})
            count = len(resp_json)
        elif isinstance(resp_json, dict):
            action_result.add_data(resp_json)
            # best-effort item count
            items = resp_json.get("items")
            if isinstance(items, list):
                count = len(items)
        else:
            action_result.add_data({"response": resp_json})

        summary = {"count": count}
        if isinstance(resp_json, dict) and resp_json.get("id") is not None:
            summary["id"] = resp_json.get("id")
        action_result.update_summary(summary)
        return action_result.set_status(phantom.APP_SUCCESS)

    
    def _handle_lockout_user(self, param):
        action_result = self.add_action_result(ActionResult(dict(param)))

        username = (param.get("username") or "").strip()
        if not username:
            return action_result.set_status(phantom.APP_ERROR, "Missing required parameter: username")

        config = self.get_config()
        api_key = config.get("api_key")
        if not api_key:
            return action_result.set_status(phantom.APP_ERROR, "Missing 'api_key' in asset configuration")

        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "api_key": api_key
        }

        self.save_progress("Locking out user: {0}".format(username))

        ret_val, resp_json = self._make_rest_call(
            endpoint="/sera/v2/ransomware/lockout/{0}".format(username),
            action_result=action_result,
            method="post",
            headers=headers,
            json={}
        )

        if phantom.is_fail(ret_val):
            return action_result.get_status()

        action_result.add_data(resp_json if isinstance(resp_json, dict) else {"response": resp_json})
        action_result.update_summary({"username": username})
        return action_result.set_status(phantom.APP_SUCCESS)

    def _handle_unlock_user(self, param):
        action_result = self.add_action_result(ActionResult(dict(param)))

        username = (param.get("username") or "").strip()
        if not username:
            return action_result.set_status(phantom.APP_ERROR, "Missing required parameter: username")

        config = self.get_config()
        api_key = config.get("api_key")
        if not api_key:
            return action_result.set_status(phantom.APP_ERROR, "Missing 'api_key' in asset configuration")

        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "api_key": api_key
        }

        self.save_progress("Unlocking user: {0}".format(username))

        ret_val, resp_json = self._make_rest_call(
            endpoint="/sera/v2/ransomware/unlock/{0}".format(username),
            action_result=action_result,
            method="post",
            headers=headers,
            json={}
        )

        if phantom.is_fail(ret_val):
            return action_result.get_status()

        action_result.add_data(resp_json if isinstance(resp_json, dict) else {"response": resp_json})
        action_result.update_summary({"username": username})
        return action_result.set_status(phantom.APP_SUCCESS)


    def _handle_test_connectivity(self, param):
        action_result = self.add_action_result(ActionResult(dict(param)))

        self.save_progress("Testing connectivity to IGLS healthcheck endpoint")

        headers = {"Accept": "text/plain"}

        ret_val, response = self._make_rest_call(
            endpoint="/sera/v1/healthcheck",
            action_result=action_result,
            method="get",
            headers=headers
        )

        if phantom.is_fail(ret_val):
            self.save_progress("Test Connectivity FAILED")
            return action_result.get_status()

        self.save_progress("Test Connectivity PASSED")

        # Store the plain text response
        if isinstance(response, dict):
            action_result.add_data(response)
        else:
            action_result.add_data({"response": response})

        action_result.update_summary({"status": "reachable"})
        return action_result.set_status(phantom.APP_SUCCESS)

    def handle_action(self, param):
        action_id = self.get_action_identifier()
        self.debug_print("action_id", action_id)

        if action_id == 'test_connectivity':
            return self._handle_test_connectivity(param)

        elif action_id == 'create_data_snapshots':
            return self._handle_create_data_snapshots(param)

        elif action_id == 'lockout_user':
            return self._handle_lockout_user(param)

        elif action_id == 'unlock_user':
            return self._handle_unlock_user(param)

        return self.set_status(phantom.APP_ERROR, "Unsupported action: {0}".format(action_id))


    def initialize(self):
        self._state = self.load_state()

        config = self.get_config()
        self._base_url = config.get('base_url')

        return phantom.APP_SUCCESS

    def finalize(self):
        self.save_state(self._state)
        return phantom.APP_SUCCESS


# Minimal local test harness (optional). No SOAR UI login, no username/password.
def main():
    import argparse

    argparser = argparse.ArgumentParser()
    argparser.add_argument('input_test_json', help='Input Test JSON file')
    args = argparser.parse_args()

    with open(args.input_test_json) as f:
        in_json = json.loads(f.read())
        print(json.dumps(in_json, indent=4))

        connector = SupernaConnector()
        connector.print_progress_message = True

        ret_val = connector._handle_action(json.dumps(in_json))
        print(json.dumps(json.loads(ret_val), indent=4))


if __name__ == '__main__':
    main()