from typing import Optional, TYPE_CHECKING, Type

import asyncio
from copy import deepcopy

from shimoku.api.base_resource import Resource
from shimoku.api.resources.data_set import DataSet, Mapping
if TYPE_CHECKING:
    from shimoku.api.resources.app import App

import logging
logger = logging.getLogger(__name__)


class Report(Resource):
    """ Report resource class """

    _module_logger = logger
    resource_type = 'report'
    plural = 'reports'
    alias_field = 'properties/hash'

    report_type = None
    possible_values = {}
    default_properties = {
        'hash': None,
    }

    class ReportDataSet(Resource):

        _module_logger = logger
        resource_type = 'reportDataSet'
        plural = 'reportDataSets'

        def __init__(self, parent: 'Report', uuid: Optional[str] = None, db_resource: Optional[dict] = None):

            params = dict(
                reportId=parent['id'],
                dataSetId='',
                properties={},
            )

            super().__init__(parent=parent, uuid=uuid, db_resource=db_resource, params=params,
                             check_params_before_creation=['reportId', 'dataSetId'],
                             params_to_serialize=['properties'])

        async def delete(self):
            """ Delete the report dataset """
            return await self._base_resource.delete()

        async def update(self):
            """ Update the report dataset """
            return await self._base_resource.update()

    def __init__(self, parent: 'App', uuid: Optional[str] = None, db_resource: Optional[dict] = None):

        params = dict(
            title=None,
            path=None,
            pathOrder=None,
            reportType=self.report_type,
            order=0,
            sizeColumns=12,
            sizeRows=3,
            sizePadding='0,0,0,0',
            bentobox={},
            properties=deepcopy(self.default_properties),
            dataFields={},
            chartData=[],
            # subscribed=False,
            smartFilters=[],
        )

        super().__init__(parent=parent, uuid=uuid, db_resource=db_resource, params=params,
                         children=[Report.ReportDataSet],
                         check_params_before_creation=['order'],
                         params_to_serialize=['properties', 'dataFields', 'chartData', 'bentobox', 'smartFilters'])

    def __ne__(self, other):
        return not self.__eq__(other)

    def __eq__(self, other: 'Report'):
        return super().__eq__(other)

    def __new__(cls, parent: 'App', uuid: Optional[str] = None, db_resource: Optional[dict] = None):
        if uuid:
            return super().__new__(cls)
        if cls is Report:
            if db_resource is None:
                raise ValueError('You must provide a db_resource to create a Report instance')
            if db_resource['reportType'] == 'TABS':
                from .reports.tabs_group import TabsGroup
                return TabsGroup(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'MODAL':
                from .reports.modal import Modal
                return Modal(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'INDICATOR':
                from .reports.charts.indicator import Indicator
                return Indicator(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'ECHARTS2':
                from .reports.charts.echart import EChart
                return EChart(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'IFRAME':
                from .reports.charts.iframe import IFrame
                return IFrame(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'HTML':
                from .reports.charts.html import HTML
                return HTML(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'TABLE':
                from .reports.charts.table import Table
                return Table(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'ANNOTATED_ECHART':
                from .reports.charts.annotated_chart import AnnotatedEChart
                return AnnotatedEChart(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'BUTTON':
                from .reports.charts.button import Button
                return Button(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'FORM':
                from .reports.charts.input_form import InputForm
                return InputForm(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] == 'FILTERDATASET':
                from .reports.filter_data_set import FilterDataSet
                return FilterDataSet(parent=parent, uuid=uuid, db_resource=db_resource)
            elif db_resource['reportType'] in ['INDICATORS', 'MULTIFILTER', 'ECHARTS', None]:
                from .reports.unsupported import Unsupported
                return Unsupported(parent=parent, uuid=uuid, db_resource=db_resource)
            else:
                raise ValueError(f'Unknown report type {db_resource["reportType"]}')
        else:
            return super().__new__(cls)

    async def delete(self):
        """ Delete the report """
        return await self._base_resource.delete()

    async def update(self):
        """ Update the report """
        return await self._base_resource.update()

    def set_properties(self, **properties):
        """ Set the properties of the report without saving it to the server ç
        :param properties: the properties to set """
        for property_name, value in properties.items():
            if property_name not in self.default_properties.keys():
                raise ValueError(f'Property {property_name} is not a possible property for {self.report_type}')
            if property_name in self.possible_values and value not in self.possible_values[property_name]:
                raise ValueError(f'Value {value} is not a possible value for property {property_name}')

        self['properties'].update(properties)
        self._base_resource.changed_params.add('properties')

    async def change_report_type(self, report_class: Type['Report']):
        """ change the report type of the report """
        logger.info(f'Changing component type from {self.report_type} to {report_class.report_type}')
        if self.report_type == report_class.report_type:
            return
        await self.delete_report_data_sets()
        self['reportType'] = report_class.report_type
        r_hash = self['properties'].get('hash')
        self['properties'] = deepcopy(report_class.default_properties)
        self['properties']['hash'] = r_hash
        self['dataFields'] = {}
        self['chartData'] = []
        self.__class__ = report_class

    # ReportDataSet methods
    async def create_report_data_set(self, mapping_data_set_sort: tuple[Optional[Mapping], DataSet, Optional[dict]],
                                     properties: Optional[dict] = None) -> 'ReportDataSet':
        """ Update the report dataset
        :param mapping_data_set_sort: a tuple of mapping, data_set, sort
        :param properties: the properties to set
        :return: the updated report dataset
        """
        mapping, data_set, sort = mapping_data_set_sort
        data_set_id = data_set['id']
        properties = properties if properties is not None else {}
        properties['mapping'] = mapping
        properties['sort'] = sort
        params = dict(
            dataSetId=data_set_id,
            properties=properties,
        )
        report_data_set = await self._base_resource.create_child(self.ReportDataSet, **params)
        return report_data_set

    async def get_report_dataset(self, uuid: Optional[str] = None) -> 'ReportDataSet':
        """ Get a report dataset
        :param uuid: the uuid of the report dataset to get
        :return: the report dataset
        """
        return await self._base_resource.get_child(self.ReportDataSet, uuid=uuid)

    async def get_report_data_sets(self) -> list['ReportDataSet']:
        """ Get all the report datasets of the report """
        return await self._base_resource.get_children(self.ReportDataSet)

    async def delete_report_dataset(self, uuid: Optional[str] = None) -> bool:
        """ Delete a report dataset
        :param uuid: the uuid of the report dataset to delete
        :return: True if the report dataset was deleted, False otherwise
        """
        return await self._base_resource.delete_child(self.ReportDataSet, uuid=uuid)

    async def delete_report_data_sets(self, log: bool = False):
        """ Delete the datasets and links that are not in use anymore.
        :param log: if True, log the actions"""
        report_data_sets = await self.get_report_data_sets()
        await asyncio.gather(*[self._base_resource.delete_child(self.ReportDataSet, uuid=rds['id'])
                               for rds in report_data_sets])
        if log:
            logger.info(f'Deleted {len(report_data_sets)} component data set links from component at {str(self)}')
