# pylint: disable=no-self-use
import json
import time
from datetime import datetime
from urllib.error import URLError

import pandas as pd
import requests
from dateutil import parser

from sdc_dp_helpers.api_utilities.date_managers import date_handler
from sdc_dp_helpers.api_utilities.file_managers import load_file
from sdc_dp_helpers.api_utilities.retry_managers import retry_handler, request_handler


class CustomOneSignalReader:
    def __init__(self, creds_file, config_file):
        self._creds = load_file(creds_file, "yml")
        self._config = load_file(config_file, "yml")

        self._header = {
            "Content-Type": "application/json; charset=utf-8",
            "Authorization": f"Basic {self._creds.get('api_key')}",
            "User-Agent": "Mozilla/5.0",
        }

        self._request_session = requests.Session()
        self.offset, self.data_set = 0, []

    @request_handler(wait=1, backoff_factor=0.5)
    @retry_handler(
        exceptions=ConnectionError,
        total_tries=5,
        should_raise=True,
        initial_wait=30,
        backoff_factor=1,
    )
    def _view_notifications_handler(self, offset):
        """
        Query handler for view_notifications.
        https://documentation.onesignal.com/reference/view-notifications

        View the details of multiple notifications.
        OneSignal periodically deletes records of API notifications
        older than 30 days.
        If you would like to export all notification data to CSV,
        you can do this through the dashboard.

        ⚠ Note that adding any date oriented payload drastically affects
        the output data. Even if the payload values are null.
        """
        response = requests.get(
            url=f"https://onesignal.com/api/v1/notifications?app_id={self._creds.get('app_id')}"
            f"&limit={self._config.get('limit', 50)}"
            f"&offset={offset}",
            headers=self._header,
        )

        resp_status_code, resp_reason = response.status_code, response.reason
        print(
            f"View Notification: Offset: {offset}/{response.json().get('total_count')}. "
            f"Status: {resp_status_code} - {resp_reason}."
        )
        if resp_status_code == 200:
            return response.json()
        raise ConnectionError(
            f"View Notification request failed [{resp_status_code}] - {resp_reason}."
        )

    @retry_handler(
        exceptions=ConnectionError,
        total_tries=2,
        should_raise=True,
        initial_wait=30,
        backoff_factor=1,
    )
    def _csv_export_handler(self):
        """
        Query handler for csv_export.
        https://documentation.onesignal.com/reference/csv-export

        Generate a compressed CSV export of all of your current user data.
        This method can be used to generate a compressed CSV export of all
        of your current user data. It is a much faster alternative than
        retrieving this data using the /players API endpoint.
        The file will be compressed using GZip.
        The file may take several minutes to generate depending on the number
        of users in your app.
        The URL generated will be available for 3 days and includes random v4
        uuid as part of the resource name to be unguessable.

        ⚠ Note that adding any date oriented payload drastically affects
        the output data. Even if the payload values are null.
        """
        response = self._request_session.post(
            url=f"https://onesignal.com/api/v1/players/csv_export?app_id={self._creds.get('app_id')}",
            headers=self._header,
        )

        resp_status_code, resp_reason = response.status_code, response.reason
        if resp_status_code == 200:
            attempts = 0
            while True:
                try:
                    print(f"CSV File URL: {response.json()['csv_file_url']}.")
                    # gather & decompress csv data and turn to manageable json
                    data_frame: pd.DataFrame = pd.read_csv(
                        response.json()["csv_file_url"]
                    )
                    data_frame.to_json("tmp.json", orient="records")

                    # add a usable date field to the dataset
                    dataset = []
                    for row in json.loads(data_frame.to_json(orient="records")):
                        row["created_at_date"] = parser.parse(
                            row.get("created_at")
                        ).strftime("%Y%m%d")
                        dataset.append(row)

                    return dataset

                except URLError as err:
                    # handle the wait time for the CSV file to be generated by Onesignals Athena wrapper
                    print(
                        f"CSV file is not yet generated [{resp_status_code}] - {resp_reason}.\n"
                        "Waiting for 60 seconds..."
                    )
                    time.sleep(60)

                    if attempts > 10:
                        raise ConnectionError(
                            "CSV Export request failed after 10 attempts"
                            f" [{resp_status_code}] - {resp_reason}.\n"
                            "Please contact Onesignal for support."
                        )
                    attempts += 1
        else:
            raise ConnectionError(
                f"CSV Export request failed [{resp_status_code}] - {resp_reason}."
            )

    def run_query(self):
        """
        The Onesignal API provides programmatic methods to
        access data in Onesignal for view_notifications and csv_exports.
        """
        _endpoint = self._config.get("endpoint")
        _parallel = self._config.get("parallel", False)

        _date_fmt = "%Y-%m-%d"
        start_date = date_handler(self._config.get("start_date", None), _date_fmt)
        end_date = date_handler(self._config.get("end_date", None), _date_fmt)

        if _endpoint == "csv_export":
            return self._csv_export_handler()

        elif _endpoint == "view_notifications":
            # process all rows of data and ensure only relevant fields are
            # collected to be returned and written to s3
            loop_bool, offset = True, 0
            while loop_bool:
                response_data = self._view_notifications_handler(offset=offset)
                data = response_data.get("notifications", None)
                if data is None:
                    loop_bool = False
                for row in data:
                    # the data is sorted by completed_at DESC, so if we go out of date range, end the query
                    # this is an epoch date, so handling it accordingly
                    try:
                        completed_at = datetime.utcfromtimestamp(
                            row.get("completed_at")
                        ).strftime(_date_fmt)
                        if start_date <= completed_at <= end_date:
                            self.data_set.append(row)
                        else:
                            loop_bool = False
                            print("No more view_notification for given date ranges.")
                            break
                    except TypeError as err:
                        loop_bool = False
                        print("No more view_notification for given date ranges.")
                        break
                offset += 1

            return self.data_set
        else:
            raise EnvironmentError(f"Endpoint {_endpoint} is not supported.")
