import shutil
import time
from threading import Lock

from parallels.core import messages
from parallels.core.logging import get_logger
from parallels.core.reports.model.issue import Issue
from parallels.core.reports.report_writer import ReportWriter
from parallels.core.utils.json_utils import read_json, write_json

logger = get_logger(__name__)


class PersistentReportWriter(ReportWriter):
    """Persistent report - report where all data is stored to a file, and read from the file on the next execution.

    Issues are always appended to the report. So if you run one command, for example "transfer-accounts" and
    subscription gets 3 issues, and then run another command, for example "copy-content", and subscription gets
    2 issues, then this report will contain 5 issues for that subscription.
    """
    def __init__(self, report, filename):
        """Class constructor

        :type report: parallels.core.reports.model.report.Report
        :type filename: str | unicode
        """
        self._report = report
        self._serializer = ReportSerializer(filename, self)
        self._serializer.read()

    def add_issue(
        self, issue_id, severity, problem_text, solution_text=None,
        affected_objects=None, solution_type=None,
        solution_url=None, solution_url_text=None, solution_component_name=None,
        solution_download_rpc_agent=False, execution_command=None
    ):
        """Add issue to report, by issue parameters

        :rtype: None
        """
        self._report.add_issue(
            issue_id, severity, problem_text, solution_text,
            affected_objects, solution_type,
            solution_url, solution_url_text, solution_component_name,
            solution_download_rpc_agent, execution_command=execution_command
        )
        self._serializer.write()

    def add_issue_obj(self, issue):
        """Add issue to the report

        :type issue: parallels.core.reports.model.issue.Issue
        :rtype: None
        """
        self._report.add_issue_obj(issue)
        self._serializer.write()

    def subtarget(self, type, name):
        """Get child report by its type and name

        :type type: str | unicode
        :type name: str | unicode | None
        :rtype: parallels.core.reports.persistent_report_writer.PersistentSubreportWriter
        """
        return PersistentSubreportWriter(self._report.subtarget(type, name), self._serializer)

    @property
    def report(self):
        return self._report


class PersistentSubreportWriter(ReportWriter):
    def __init__(self, report, serializer=None):
        """Class constructor

        :type report: parallels.core.reports.model.report.Report
        :type serializer: parallels.core.reports.persistent_report_writer.ReportSerializer
        """
        self._report = report
        self._serializer = serializer

    def add_issue(
        self, issue_id, severity, problem_text, solution_text=None,
        affected_objects=None, solution_type=None,
        solution_url=None, solution_url_text=None, solution_component_name=None,
        solution_download_rpc_agent=False, execution_command=None
    ):
        """Add issue to report, by issue parameters

        :rtype: None
        """
        self._report.add_issue(
            issue_id, severity, problem_text, solution_text,
            affected_objects, solution_type,
            solution_url, solution_url_text, solution_component_name,
            solution_download_rpc_agent, execution_command=execution_command
        )
        self._serializer.write()

    def add_issue_obj(self, issue):
        """Add issue to the report

        :type issue: parallels.core.reports.model.issue.Issue
        :rtype: None
        """
        self._report.add_issue_obj(issue)
        self._serializer.write()

    def subtarget(self, type, name):
        """Get child report by its type and name

        :type type: str | unicode
        :type name: str | unicode | None
        :rtype: parallels.core.reports.persistent_report_writer.PersistentSubreportWriter
        """
        return PersistentSubreportWriter(self._report.subtarget(type, name), self._serializer)


class ReportSerializer(object):
    def __init__(self, filename, persistent_report):
        """Class constructor

        :type filename: str | unicode
        :type persistent_report: parallels.core.reports.persistent_report_writer.PersistentReportWriter
        """
        self._lock = Lock()
        self._filename = filename
        self._persistent_report = persistent_report

    def write(self):
        """Write the report to the file

        :rtype: None
        """
        with self._lock:
            write_json(
                self._filename, self._persistent_report.report.as_dictionary(recursive=True), pretty_print=True
            )

    def read(self):
        """Read report data from the file to the report

        :rtype: None
        """
        with self._lock:
            try:
                data = read_json(self._filename)
            except Exception as e:
                logger.exception()
                data = None

                moved_filename = '%s.invalid.%s' % (self._filename, int(time.time()))
                try:
                    shutil.move(self._filename, moved_filename)
                    moved = True
                except Exception:
                    logger.exception()
                    moved = False

                if not moved:
                    logger.fwarn(messages.ERROR_READING_REPORT_FILE, filename=self._filename, reason=e)
                else:
                    logger.fwarn(
                        messages.ERROR_READING_REPORT_FILE_MOVED,
                        filename=self._filename, reason=e, moved_filename=moved_filename
                    )

            if data is None:
                return

            self._unserialize_report(data, self._persistent_report.report)

    def _unserialize_report(self, data, report):
        """Unserialize report from data dictionary

        :type data: dict
        :type report: parallels.core.reports.persistent_report.PersistentReport |
            parallels.core.reports.persistent_report.PersistentSubreport
        :rtype: None
        """
        report.issues = self._unserialize_issues(data.get('issues'))
        for child_data in data.get('children'):
            child_report = report.subtarget(
                child_data.get('type'), child_data.get('name')
            )
            self._unserialize_report(child_data, child_report)

    @staticmethod
    def _unserialize_issues(issues_data):
        """Unserialize issues from data list

        :type issues_data: list[dict]
        :rtype: list[parallels.core.reports.model.issue.Issue]
        """
        return [
            Issue(**issue_data) for issue_data in issues_data
        ]
