#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Collection of decorators to check several type of errors in
specific types of functions.

"""

# System modules
import socket

# Third-party modules
import requests
from pyvantagepro.device import NoDeviceException

# Other templates
from ..utils.logger import log

# class ManualRetry(Exception):
#     '''
#     Manually raise an exception, so it is possible to handle a retry
#     '''


def content_errors(content):
    '''
    Decorator for handling exceptions.
    Put it on top of the content() method
    in the shinsapp class.
    '''

    def content_errors_inner(*args, **kwargs):
        '''
        Modifies the content() function
        '''

        this = args[0]  # It's the "self"

        try:
            '''
            This is the content method in basetemplate
            which is implemented in each template.
            If all goes well, it is executed smoothly.
            '''
            content(*args, **kwargs)

        except Exception as e:
            '''
            If an error occurs, the template is paused at the
            beginning of the following paragraph.
            '''

            log.error(f"CONTENT: exception {e}")
            this.paused = True

    return content_errors_inner


def request_errors(func):
    '''
    Decorator for handling exceptions on a class method.
    Put it on top of the class method.
    '''
    def request_errors_inner(*args, **kwargs):
        '''
        Modifies the func() method.
        '''

        this = args[0]  # it's the "self"
        name = str(args[0].__class__)

        try:
            this.error = []
            res = func(*args, **kwargs)
            return res

        except requests.exceptions.HTTPError as e:
            res = e.response

            if res.status_code == 400:
                # API returns body like: '0x8000100a\r\nParameter(s)
                # missing.\r\n'
                try:
                    # Attempt to parse the API-specific error
                    body_lines = res.text.strip().split('\r\n')
                    error_code = body_lines[0]
                    error_message = body_lines[1] if len(
                        body_lines) > 1 else "No error message provided."
                    msg = f"{name}: Bad Request - API Error {error_code}: {error_message}"
                    log.error(msg)
                    this.error.append(msg)
                except (IndexError, AttributeError):
                    # Fallback for unexpected 400 error format
                    msg = f"{name}: Bad Request with unparsable body."
                    log.error(msg)
                    log.error(f"Raw response: {res.text}")
                    this.error.append(msg)

            elif res.status_code == 500:
                msg = f"{name}: Internal Server Error on device."
                log.error(msg)
                log.error(f"Reason: {res.reason}")
                log.error(f"Response: {res.text}")
                this.error.append(msg)
                this.error.append(res.text)

            else:  # 404 Not Found, etc.
                msg = f"{name}: HTTP Error - {e}"
                log.error(msg)
                this.error.append(str(e))

            return

        except (requests.exceptions.Timeout, requests.exceptions.ConnectTimeout) as e:
            msg = f"{name}: Timeout or Connection timeout!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return []

        except requests.exceptions.TooManyRedirects as e:
            msg = f"{name}: Bad request!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except ConnectionRefusedError as e:
            msg = f"{name}: Connection refused!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except requests.exceptions.ConnectionError as e:
            msg = f"{name}: Connection error!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return []

        except AttributeError as e:
            msg = f"{name}: No data!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except AssertionError as e:
            msg = f"{name}: Probably cabinet not ready!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except TypeError as e:
            msg = f"{name}: Probably subsystem not responding!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except requests.exceptions.RequestException as e:
            msg = f"{name}: Generic request exception!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            raise SystemExit(e) from e

        except Exception as e:
            msg = f"{name}: Not handled error!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            raise SystemExit(e) from e

    return request_errors_inner


def telnet_errors(func):
    '''
    Decorator for handling exceptions on a class method.
    Put it on top of the class method.
    '''
    def telnet_errors_inner(*args, **kwargs):
        '''
        Modifies the func() method.
        '''

        this = args[0]  # it's the "self"
        name = str(args[0].__class__)

        try:
            this.error = []
            return func(*args, **kwargs)

        except socket.gaierror as e:
            msg = f"{name}: Server not found!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except socket.timeout as e:
            msg = f"{name}: Server timeout!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except AttributeError as e:
            msg = f"No attribute with this name in {name}"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return

        except BrokenPipeError as e:
            msg = f"{name}: Probably sent request to telnet while waiting previous response"
            log.debug(msg)
            log.debug(e)
            log.error(e)

        except IOError as e:
            msg = f"{name}: Probably failed getting info from telnet"
            log.debug(msg)
            log.debug(e)
            log.error(e)

        except Exception as e:
            msg = f"{name}: Not handled error"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            raise SystemExit(e)

    return telnet_errors_inner


def ascom_errors(func):
    '''
    Decorator for handling exceptions on a class method.
    Put it on top of the class method.
    '''
    def ascom_errors_inner(*args, **kwargs):
        '''
        Modifies the func() method.
        '''

        this = args[0]  # it's the "self"
        name = str(args[0].__class__)

        try:
            this.error = []
            return func(*args, **kwargs)

        except Exception as e:
            msg = f"{name}: Not handled error"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            raise SystemExit(e)

    return ascom_errors_inner


def meteo_errors(func):
    '''
    Decorator for handling exceptions on a class method.
    Put it on top of the class method.
    '''
    def meteo_errors_inner(*args, **kwargs):
        '''
        Modifies the func() method.
        '''

        this = args[0]  # it's the "self"
        name = str(args[0].__class__)

        try:
            this.error = []
            return func(*args, **kwargs)

        except NoDeviceException as e:
            msg = f"{name}: Cannot connect to meteo station"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))

        except BrokenPipeError as e:
            msg = f"{name}: Probably sent request to meteo station while waiting previous response"
            log.debug(msg)
            log.debug(e)

        except IOError as e:
            msg = f"{name}: Probably failed getting info from Meteo station"
            log.debug(msg)
            log.debug(e)

        except Exception as e:
            msg = f"{name}: Not handled error"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            raise SystemExit(e)

    return meteo_errors_inner


def socket_errors(func):
    '''
    Decorator for handling exceptions on a class method.
    Put it on top of the class method.
    '''
    def socket_errors_inner(*args, **kwargs):
        '''
        Modifies the func() method.
        '''

        this = args[0]  # it's the "self"
        name = str(args[0].__class__)

        try:
            this.error = []
            return func(*args, **kwargs)

        except Exception as e:
            msg = f"{name}: Not handled exception!"
            log.error(msg)
            log.error(e)
            this.error.append(msg)
            this.error.append(str(e))
            return
