#!/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

# Custom modules
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:
                #'0x80001005\r\nThe Error Message\r\n'
                msg = f"{name}: Device error"
                log.error(msg)
                this.error.append(res.text.replace("\r\n", " "))
            elif res.status_code == 500:
                log.error(res.reason)  # Device error
                log.error(res.text)
                this.error.append(res.reason)
                this.error.append(res.text)
            else:  # 404
                log.error(e)  # Not found
                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 pyvantagepro.device.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
