"""
Network Tab Logging Library for Robot Framework Tests

This library provides functionality to capture and print network requests
that occur during test execution using the existing WebDriver instance.
"""

import json
import base64
import html
from typing import Optional, List, Dict, Any
from robot.api import logger as robotlogger
from accelarobotkeywords import SeleniumKeywords


class NetworkLogger:

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        self.network_logs = []

    @staticmethod
    def _get_driver_instance():
        driver = SeleniumKeywords._driver
        if driver is None:
            raise Exception("No active WebDriver instance found.")
        try:
            network_logs = driver.get_log('performance')
            robotlogger.info("Retrieved %d log entries" % len(network_logs))
        except Exception as e:
            robotlogger.warn("Performance logging not available: %s. Returning empty list: " % str(e))
            network_logs = []
        
        return driver, network_logs

    @staticmethod
    def _parse_log_entry(log_entry: Dict) -> Optional[Dict[str, Any]]:
        try:
            outer_message = json.loads(log_entry['message'])
            return outer_message['message']
        except (json.JSONDecodeError, KeyError) as e:
            robotlogger.debug("Failed to parse outer_message log entry: %s" % str(e))
            return None

    def _build_requests_map(self, raw_logs: List[Dict]) -> Dict[str, Dict]:
        requests_map = {}

        for log_entry in raw_logs:
            message = self._parse_log_entry(log_entry)
            if not message:
                continue

            method = message.get('method', '')
            params = message.get('params', {})

            if method == 'Network.requestWillBeSent':
                self._handle_request(requests_map, params)
            elif method == 'Network.responseReceived':
                self._handle_response(requests_map, params)

        return requests_map

    @staticmethod
    def _handle_request(requests_map: Dict, params: Dict):
        """Handle Network.requestWillBeSent event."""
        request_id = params.get('requestId', '')
        request = params.get('request', {})
        post_data = request.get('postData', '')
        if post_data and '&' in post_data:
            try:
                post_data = html.unescape(post_data)
            except Exception as e:
                robotlogger.debug("Could not unescape post_data: %s" % str(e))
        
        requests_map[request_id] = {
            'url': request.get('url', ''),
            'method': request.get('method', ''),
            'post_data': post_data,
            'headers': request.get('headers', {}),
            'timestamp': params.get('timestamp', 0),
            'response_received': False
        }

    @staticmethod
    def _handle_response(requests_map: Dict, params: Dict):
        """Handle Network.responseReceived event."""
        request_id = params.get('requestId', '')
        response = params.get('response', {})

        if request_id in requests_map:
            requests_map[request_id].update({
                'response_url': response.get('url', ''),
                'status': response.get('status', 'N/A'),
                'status_text': response.get('statusText', 'N/A'),
                'mime_type': response.get('mimeType', 'N/A'),
                'response_headers': response.get('headers', {}),
                'response_received': True,
                'request_id': request_id
            })

    @staticmethod
    def _matches_filters(req_data: Dict, url_substring: Optional[str],
                        payload_substring: Optional[str]) -> bool:
        if not req_data.get('response_received', False):
            return False

        if url_substring and url_substring not in req_data.get('url', ''):
            return False

        if payload_substring:
            post_data = req_data.get('post_data', '')
            if payload_substring not in post_data:
                return False

        return True

    @staticmethod
    def _get_response_body(driver, request_id: str) -> str:
        BINARY_BODY = "[Binary content - not displayable as text]"
        try:
            result = driver.execute_cdp_cmd(
                "Network.getResponseBody",
                {"requestId": request_id}
            )
            body = result.get("body", "")

            if result.get("base64Encoded", False):
                raw = base64.b64decode(body)

                try:
                    return raw.decode("utf-8")
                except UnicodeDecodeError:
                    try:
                        decoded = raw.decode("latin-1")
                        robotlogger.debug(
                            "Response body for request %s decoded using latin-1" % request_id
                        )
                        return decoded
                    except Exception:
                        return BINARY_BODY
            return body

        except Exception as e:
            return BINARY_BODY

    @staticmethod
    def _format_json(data: str) -> str:
        try:
            parsed = json.loads(data)
            return json.dumps(parsed, indent=2)
        except (json.JSONDecodeError, ValueError):
            return data


    def print_network_requests(self, url_substring: Optional[str] = None,
                               payload_substring: Optional[str] = None) -> List[Dict]:
        driver, raw_logs = self._get_driver_instance()
        self.network_logs = raw_logs

        if not raw_logs:
            robotlogger.info("No network requests captured")
            return []

        robotlogger.info("Processing %d captured network requests" % len(raw_logs))

        requests_map = self._build_requests_map(raw_logs)

        matched_requests = []

        for request_id, req_data in requests_map.items():
            if not self._matches_filters(req_data, url_substring, payload_substring):
                continue

            response_body = self._get_response_body(driver, request_id)
            req_data['response_body'] = response_body

            matched_requests.append(req_data.copy())

        robotlogger.info("Total matches found: %d" % len(matched_requests))


        return matched_requests
