#!/usr/bin/env python3

from abc import ABC, abstractmethod
from typing import Any, Dict

from unicodecsv import writer
from unicodecsv.py3 import UnicodeWriter


class FailedOperationException(Exception):
    def __init__(
        self,
        reporter: "Reporter",
        err_msg: str,
        operation_name: str,
        variables: Dict[str, Any],
    ) -> None:
        self.reporter = reporter
        self.err_msg = err_msg
        self.operation_name = operation_name
        self.variables = variables

    def log_failed_operation(self, row_identifier: str, row: Dict[str, Any]) -> None:
        self.reporter.log_failed_operation(row_identifier, row, self)

    def __str__(self) -> str:
        return self.err_msg


class Reporter(ABC):
    @abstractmethod
    def log_successful_operation(
        self,
        operation_name: str,
        variables: Dict[str, Any],
        network_time: float,
        decode_time: float,
    ) -> None:
        pass

    @abstractmethod
    def log_failed_operation(
        self, row_identifier: str, row: Dict[str, Any], e: FailedOperationException
    ) -> None:
        pass


class CSVReporter(Reporter):
    def __init__(self, out_file_path: str, err_file_path: str) -> None:

        """Reporting utility for the SymphonyClient to report on
        successful and failed operations.
        In order to report on failed operation, user is required to catch
        FailedOperationException and call logFailedOperation with date
        identifier (row number) & full data for easier debugging later.

        Args:
            out_file_path (str): Path to write csv of successful operations.
            err_file_path (str): Path to write csv of failed operations.

        Example:
        ```
        reporter = CSVReporter(csvOutPath, csvErrPath)
        client = GraphqlClient("http://graph/query", session, "client/1.0", reporter=reporter)
        try:
            location = client.call(..)
        except FailedOperationException as e:
            e.logFailedOperation(data_identifier, data)
        ```

        """

        # pyre-fixme[8]: Attribute has type `UnicodeWriter`; used as `_writer`.
        self.out_file: UnicodeWriter = writer(
            # pyre-fixme[6]: Expected `_Writer` for 1st param but got `BufferedWriter`.
            open(out_file_path, "wb"),
            encoding="utf-8",
        )
        self.out_file.writerow(
            ["operation_name", "variables", "network_time", "decode_time"]
        )

        # pyre-fixme[8]: Attribute has type `UnicodeWriter`; used as `_writer`.
        self.err_file: UnicodeWriter = writer(
            # pyre-fixme[6]: Expected `_Writer` for 1st param but got `BufferedWriter`.
            open(err_file_path, "wb"),
            encoding="utf-8",
        )
        self.err_file.writerow(
            [
                "operation_name",
                "variables",
                "error_msg",
                "row_identifier",
                "row",
            ]
        )

    def log_successful_operation(
        self,
        operation_name: str,
        variables: Dict[str, Any],
        network_time: float,
        decode_time: float,
    ) -> None:
        self.out_file.writerow(
            [operation_name, str(variables), network_time, decode_time]
        )

    def log_failed_operation(
        self, row_identifier: str, row: Dict[str, Any], e: FailedOperationException
    ) -> None:
        self.err_file.writerow(
            [
                e.operation_name,
                str(e.variables),
                e.err_msg,
                row_identifier,
                str(row),
            ]
        )


class PrintReporter(Reporter):
    def log_successful_operation(
        self,
        operation_name: str,
        variables: Dict[str, Any],
        network_time: float,
        decode_time: float,
    ) -> None:
        print(
            {
                "operation_name": operation_name,
                "variables": variables,
                "network_time": network_time,
                "decode_time": decode_time,
            }
        )

    def log_failed_operation(
        self, row_identifier: str, row: Dict[str, Any], e: FailedOperationException
    ) -> None:
        print(
            {
                "operation_name": e.operation_name,
                "variables": str(e.variables),
                "error_msg": e.err_msg,
                "row_identifier": row_identifier,
                "row": str(row),
            }
        )


class DummyReporter(Reporter):
    def __init__(self) -> None:
        pass

    def log_successful_operation(
        self,
        operation_name: str,
        variables: Dict[str, Any],
        network_time: float,
        decode_time: float,
    ) -> None:
        pass

    def log_failed_operation(
        self, row_identifier: str, row: Dict[str, Any], e: FailedOperationException
    ) -> None:
        pass


DUMMY_REPORTER = DummyReporter()
