import json
import logging
from os.path import basename
from typing import List, Optional, Iterable, Dict

from enmapbox.qgispluginsupport.qps.utils import stringFromByteArray
from qgis.PyQt.QtCore import QMimeData, QUrl, QByteArray
from qgis.PyQt.QtXml import QDomNamedNodeMap, QDomDocument
from qgis.core import QgsLayerItem
from qgis.core import QgsMapLayer, QgsRasterLayer, QgsProject, QgsReadWriteContext, \
    QgsMimeDataUtils, QgsLayerTree
from .datasources.datasources import DataSource
from ..qgispluginsupport.qps.layerproperties import defaultRasterRenderer

logger = logging.getLogger(__name__)

MDF_RASTERBANDS = 'application/enmapbox.rasterbanddata'

MDF_ENMAPBOX_LAYERTREEMODELDATA = 'application/enmapbox.layertreemodeldata'
MDF_QGIS_LAYERTREEMODELDATA = 'application/qgis.layertreemodeldata'
MDF_QGIS_LAYERTREEMODELDATA_XML = 'layer_tree_model_data'

MDF_PYTHON_OBJECTS = 'application/enmapbox/objectreference'
MDF_SPECTRALLIBRARY = 'application/hub-spectrallibrary'

MDF_URILIST = 'text/uri-list'
MDF_TEXT_HTML = 'text/html'
MDF_TEXT_PLAIN = 'text/plain'

MDF_QGIS_LAYER_STYLE = 'application/qgis.style'
QGIS_URILIST_MIMETYPE = "application/x-vnd.qgis.qgis.uri"


def attributesd2dict(attributes: QDomNamedNodeMap) -> Dict[str, str]:
    d = {}
    for i in range(attributes.count()):
        attribute = attributes.item(i)
        d[attribute.nodeName()] = attribute.nodeValue()
    return d


def fromDataSourceList(dataSources: List[DataSource]):
    if not isinstance(dataSources, list):
        dataSources = [dataSources]

    from enmapbox.gui.datasources.datasources import DataSource

    uriList = []
    for ds in dataSources:
        if not isinstance(ds, DataSource):
            raise ValueError(f'Input must be a list of DataSource objects, not {ds}')

        dataItem = ds.dataItem()
        uris = dataItem.mimeUris()
        if not isinstance(dataItem, QgsLayerItem):
            uri = QgsMimeDataUtils.Uri()
            uri.name = dataItem.name()
            uri.filePath = dataItem.path()
            uri.uri = dataItem.path()
            uri.providerKey = dataItem.providerKey()
            uris = [uri]
        uriList.extend(uris)

    urlList = [QUrl.fromLocalFile(u.uri) for u in uriList]

    mimeData: QMimeData = QgsMimeDataUtils.encodeUriList(uriList)
    mimeData.setUrls(urlList)
    return mimeData


def toDataSourceList(mimeData: QMimeData) -> List[DataSource]:
    """
    Returns the list of data sources contained in a QMimeData object
    """
    uriList = QgsMimeDataUtils.decodeUriList(mimeData)
    dataSources = []
    from enmapbox.gui.datasources.manager import DataSourceFactory
    for uri in uriList:
        dataSources.extend(DataSourceFactory.create(uri))
    return dataSources


def fromLayerList(mapLayers: Iterable[QgsMapLayer]):
    """
    Converts a list of QgsMapLayers into a QMimeData object
    :param mapLayers: [list-of-QgsMapLayers]
    :return: QMimeData
    """
    tree = QgsLayerTree()
    mimeData = QMimeData()

    urls = []
    for lyr in mapLayers:
        lyr: QgsMapLayer
        tree.addLayer(lyr)
        urls.append(QUrl.fromLocalFile(lyr.source()))
    doc = QDomDocument()
    context = QgsReadWriteContext()
    node = doc.createElement(MDF_QGIS_LAYERTREEMODELDATA_XML)
    doc.appendChild(node)
    for c in tree.children():
        c.writeXml(node, context)

    mimeData.setData(MDF_QGIS_LAYERTREEMODELDATA, doc.toByteArray())

    return mimeData


def containsMapLayers(mimeData: QMimeData) -> bool:
    """
    Checks if the mimeData contains any format suitable to describe QgsMapLayers
    :param mimeData:
    :return:
    """
    valid = [MDF_RASTERBANDS, MDF_QGIS_LAYERTREEMODELDATA, QGIS_URILIST_MIMETYPE,
             MDF_URILIST]

    for f in valid:
        if f in mimeData.formats():
            return True
    return False


def extractMapLayers(mimeData: QMimeData,
                     project: Optional[QgsProject] = None) -> List[QgsMapLayer]:
    """
    Extracts QgsMapLayers from QMimeData
    :param mimeData:
    :param project:
    :return: A list if QgsMapLayers
    """
    if project is None:
        project = QgsProject.instance()
    if not isinstance(mimeData, QMimeData):
        raise ValueError(f'Input must be a QMimeData object, not {mimeData}')

    from enmapbox.gui.datasources.datasources import SpatialDataSource
    from enmapbox.gui.datasources.manager import DataSourceFactory

    newMapLayers = []

    QGIS_LAYERTREE_FORMAT = None
    if MDF_ENMAPBOX_LAYERTREEMODELDATA in mimeData.formats():
        QGIS_LAYERTREE_FORMAT = MDF_ENMAPBOX_LAYERTREEMODELDATA
    elif MDF_QGIS_LAYERTREEMODELDATA in mimeData.formats():
        QGIS_LAYERTREE_FORMAT = MDF_QGIS_LAYERTREEMODELDATA

    if QGIS_LAYERTREE_FORMAT in mimeData.formats():
        doc = QDomDocument()
        doc.setContent(mimeData.data(QGIS_LAYERTREE_FORMAT))
        node = doc.firstChildElement(MDF_QGIS_LAYERTREEMODELDATA_XML)
        context = QgsReadWriteContext()
        # context.setPathResolver(QgsProject.instance().pathResolver())
        layerTree = QgsLayerTree.readXml(node, context)

        for layerId in layerTree.findLayerIds():
            lyr = project.mapLayer(layerId)
            if isinstance(lyr, QgsMapLayer):
                newMapLayers.append(lyr)
                break

    elif MDF_RASTERBANDS in mimeData.formats():

        data = json.loads(stringFromByteArray(mimeData.data(MDF_RASTERBANDS)))

        for t in data:
            uri, baseName, providerKey, band = t
            lyr = QgsRasterLayer(uri, baseName=baseName, providerType=providerKey)
            lyr.setRenderer(defaultRasterRenderer(lyr, bandIndices=[band]))
            newMapLayers.append(lyr)

    elif MDF_ENMAPBOX_LAYERTREEMODELDATA in mimeData.formats():
        # this drop comes from the dock tree
        pass

    elif QGIS_URILIST_MIMETYPE in mimeData.formats():
        for uri in QgsMimeDataUtils.decodeUriList(mimeData):

            dataSources = DataSourceFactory.create(uri, project=project)
            for dataSource in dataSources:
                if isinstance(dataSource, SpatialDataSource):
                    lyr = dataSource.asMapLayer(project=project)
                    if lyr is None and project != QgsProject.instance():
                        lyr = dataSource.asMapLayer(project=QgsProject.instance())

                    if isinstance(lyr, QgsMapLayer):
                        # if isinstance(lyr, QgsRasterLayer):
                        #     lyr.setRenderer(defaultRasterRenderer(lyr))
                        newMapLayers.append(lyr)

    elif MDF_URILIST in mimeData.formats():
        for url in mimeData.urls():

            if basename(url.url()) == 'MTD_MSIL2A.xml':  # resolves #42
                dataSources = [None]
            elif basename(url.url()).startswith('PRS_L') and basename(url.url()).endswith('.he5'):  # resolves #100
                dataSources = [None]
            elif basename(url.url()).startswith('EMIT_L') and basename(url.url()).endswith('.nc'):  # resolves #100
                dataSources = [None]
            else:
                dataSources = DataSourceFactory.create(url)

            for dataSource in dataSources:
                if isinstance(dataSource, SpatialDataSource):
                    lyr = dataSource.asMapLayer()
                    if isinstance(lyr, QgsMapLayer):
                        if isinstance(lyr, QgsRasterLayer):
                            lyr.setRenderer(defaultRasterRenderer(lyr))
                        newMapLayers.append(lyr)
                else:
                    # check if URL is associated with an external product,
                    # if so, the product is created by running the appropriate processing algorithm
                    from enmapboxprocessing.algorithm.importproductsdraganddropsupport import tryToImportSensorProducts
                    filename = url.toLocalFile()
                    mapLayers = tryToImportSensorProducts(filename)
                    newMapLayers.extend(mapLayers)

    else:
        pass

    info = ['Extract map layers from QMimeData']
    info.append('Formats:' + ','.join(mimeData.formats()))
    info.append(f' {len(newMapLayers)} Map Layers: ' + '\n\t'.join([f'{lyr}' for lyr in newMapLayers]))
    logger.debug('\n'.join(info))

    return newMapLayers


def textToByteArray(text):
    """
    Converts input into a QByteArray
    :param text: bytes or str
    :return: QByteArray
    """

    if isinstance(text, QDomDocument):
        return textToByteArray(text.toString())
    else:
        data = QByteArray()
        data.append(text)
        return data


def textFromByteArray(data: QByteArray):
    """
    Decodes a QByteArray into a str
    :param data: QByteArray
    :return: str
    """
    return stringFromByteArray(data)
