# mypy: disable-error-code="misc"

"""Summarize from RDF."""

from collections import Counter, defaultdict
from pathlib import Path
from textwrap import dedent
from typing import Any

import click
import matplotlib.axes
import matplotlib.pyplot as plt
import rdflib
import seaborn as sns

from dalia_dif.dif13.community import get_community_labels
from dalia_dif.dif13.legacy.rdf import get_discipline_graph
from dalia_dif.dif13.predicates import RECOMMENDING_COMMUNITY_PRED, SUPPORTING_COMMUNITY_PRED
from dalia_dif.namespace import CONVERTER

__all__ = [
    "export_chart",
]

MISSING = "missing"

COUNT_OERS_SPARQL = dedent("""\
    SELECT DISTINCT (COUNT(?s) as ?count)
    WHERE {
        ?s a educor:EducationalResource .
    }
    GROUP BY ?S
""")


def count_oers(graph: rdflib.Graph) -> int:
    """Count OERs."""
    res = graph.query(COUNT_OERS_SPARQL)
    rv = int(str(next(iter(res))[0]))  # type:ignore[index]
    return rv


COUNT_LANGUAGES_SPARQL = dedent("""\
    SELECT ?s ?o
    WHERE {
        ?s a educor:EducationalResource .
        ?s dcterms:language ?o .
    }
""")


def count_languages(graph: rdflib.Graph, *, upper: int = 4) -> tuple[Counter[str], Counter[str]]:
    """Count languages."""
    res = list(graph.query(COUNT_LANGUAGES_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_LANGUAGES_SPARQL}")
    dd = defaultdict(list)
    for uuid, lang in res:
        dd[uuid].append(str(lang).removeprefix("http://lexvo.org/id/iso639-3/"))
    combine: Counter[str] = Counter(
        ", ".join(sorted(v)) if len(v) < upper else f"eng + {len(v) - 1}" if "eng" in v else len(v)
        for v in dd.values()
    )
    single: Counter[str] = Counter(s for v in dd.values() for s in v)
    return combine, single


COUNT_LICENSES_SPARQL = dedent("""\
    SELECT ?o
    WHERE {
        ?s a educor:EducationalResource .
        ?s dcterms:license ?o .
    }
""")


def count_licenses(graph: rdflib.Graph) -> Counter[str]:
    """Count licenses."""
    res = list(graph.query(COUNT_LICENSES_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_LICENSES_SPARQL}")
    return Counter(
        CONVERTER.compress(license_uri, strict=False, passthrough=True)
        .removeprefix("spdx:")
        .removesuffix("-3.0")
        .removesuffix("-4.0")
        .removesuffix("-2.0")
        .removesuffix("-1.0")
        .replace("modalia:ProprietaryLicense", "proprietary")
        for (license_uri,) in res
    )


COUNT_FILE_EXTENSIONS_SPARQL = dedent("""\
    SELECT ?s ?o
    WHERE {
        ?s a educor:EducationalResource .
        OPTIONAL { ?s dcterms:format ?o . }
    }
""")


def count_file_extensions(graph: rdflib.Graph) -> Counter[str]:
    """Count file extensions."""
    res = list(graph.query(COUNT_FILE_EXTENSIONS_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_FILE_EXTENSIONS_SPARQL}")
    return Counter(str(format_str) if format_str else MISSING for _, format_str in res)


MEDIA_TYPE_LABELS = {
    "schema:VideoObject": "video",
    "schema:Text": "text",
    "schema:PresentationDigitalDocument": "presentation",
    "schema:AudioObject": "audio",
    "modalia:Multipart": "multipart",
    "schema:ImageObject": "image",
    "modalia:Code": "code",
}

COUNT_MEDIA_TYPE_SPARQL = dedent("""\
SELECT ?o
WHERE {
    ?s a educor:EducationalResource .
    ?s modalia:hasMediaType ?o .
}
""")


def count_media_types(graph: rdflib.Graph) -> Counter[str]:
    """Count media types."""
    res = list(graph.query(COUNT_MEDIA_TYPE_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_MEDIA_TYPE_SPARQL}")
    return Counter(
        MEDIA_TYPE_LABELS[CONVERTER.compress(media_type, strict=True)].title()
        if media_type
        else MISSING
        for (media_type,) in res
    )


COUNT_TARGET_GROUPS_SPARQL = dedent("""\
    SELECT ?o
    WHERE {
        ?s a educor:EducationalResource .
        ?s modalia:hasTargetGroup ?o .
    }
""")


def count_target_groups(graph: rdflib.Graph, total: int) -> Counter[str]:
    """Count target groups."""
    res = list(graph.query(COUNT_TARGET_GROUPS_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_TARGET_GROUPS_SPARQL}")
    rv = Counter(
        _remap_target_group(CONVERTER.parse_uri(target_group, strict=True).identifier)
        if target_group
        else MISSING
        for (target_group,) in res
    )

    most, count = rv.most_common(1)[0]
    click.echo(f"The largest target group was {most} ({count:,}/{total:,}; {count / total:.1%})")
    return rv


TARGET_GROUP_RENAMES = {
    "PhDStudent": "PhD Student",
    "MastersStudent": "Master Student",
    "BachelorStudent": "Bachelor Student",
    "StudentSchool": "Bachelor Student",
    "DataSteward": "Data Steward",
    "TeacherHighEducation": "Teacher (Higher Ed.)",
    "TeacherSchool": "Teacher (Lower Ed.)",
    "ContentProvider": "Content Provider",
}


def _remap_target_group(x: str) -> str:
    return TARGET_GROUP_RENAMES.get(x, x)


LRT_MAPPING = {
    "PodcastSeries": "podcast",
    "drill_and_practice": "drill and practice",
    "bestpractices": "best practices",
    "Bestpractices": "best practices",
    "codenotebook": "code notebook",
    "Codenotebook": "code notebook",
    "softwaresourcecode": "code",
    "Softwaresourcecode": "code",
}

COUNT_LEARNING_TYPE_RESOURCES_SPARQL = dedent("""\
    SELECT ?o
    WHERE {
        ?s a educor:EducationalResource .
        ?s modalia:hasLearningType ?o .
    }
""")


def count_learning_resource_type(graph: rdflib.Graph) -> Counter[str]:
    """Count learning resource type."""
    res = list(graph.query(COUNT_LEARNING_TYPE_RESOURCES_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_LEARNING_TYPE_RESOURCES_SPARQL}")
    return Counter(
        _remap_lrt(CONVERTER.parse_uri(learning_resource_type, strict=True).identifier)
        if learning_resource_type
        else MISSING
        for (learning_resource_type,) in res
    )


def _remap_lrt(x: str) -> str:
    return LRT_MAPPING.get(x, x).replace("_", " ").title()


DISCIPLINES_RENAMES = {
    "Cultural Studies in the narrower sense": "Cultural Studies",
    "Archival and Documentation Science": "Archival and Documentation",
    "Human Medicine / Health Sciences": "Health Sciences",
    "Information and Library Sciences": "Library Sciences",
}

GET_DISCIPLINE_LABEL_SPARQL = dedent("""\
    SELECT ?s ?label WHERE {
        ?s <http://www.w3.org/2004/02/skos/core#prefLabel> ?label .
    }
""")


def get_discipline_names() -> dict[str, str]:
    """Get discipline names (LUID to name map for HSFS)."""
    dd: defaultdict[str, list[tuple[str | None, str]]] = defaultdict(list)
    for iri, label in get_discipline_graph().query(GET_DISCIPLINE_LABEL_SPARQL):
        if not isinstance(label, rdflib.Literal):
            raise ValueError
        luid = iri.removeprefix("https://w3id.org/kim/hochschulfaechersystematik/")
        dd[luid].append((label._language, DISCIPLINES_RENAMES.get(label._value, label._value)))
    return {k: min(v, key=_get_best_lang)[1] for k, v in dd.items()}


def _get_best_lang(pair: tuple[str | None, Any]) -> tuple[int, str]:
    if pair[0] == "en" or pair[0] is None:
        return 0, ""
    elif pair[0] == "de":
        return 1, ""
    else:
        return 2, pair[0]


COUNT_PROFICIENCY_LEVELS_SPARQL = dedent("""\
    SELECT ?s ?o
    WHERE {
        ?s a educor:EducationalResource .
        OPTIONAL { ?s modalia:requiresProficiencyLevel ?o . }
    }
""")


def count_proficiency_level(graph: rdflib.Graph, total: int) -> Counter[str]:
    """Count proficiency level."""
    res = list(graph.query(COUNT_PROFICIENCY_LEVELS_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_PROFICIENCY_LEVELS_SPARQL}")
    rv = Counter(
        CONVERTER.parse_uri(proficiency_level, strict=True).identifier.title()
        if proficiency_level
        else MISSING
        for subj, proficiency_level in res
    )

    most, count = rv.most_common(1)[0]
    click.echo(
        f"The largest proficiency level was {most} ({count:,}/{total:,}; {count / total:.1%})"
    )
    return rv


COUNT_DISCIPLINES_SPARQL = dedent("""\
    SELECT ?o
    WHERE {
        ?s a educor:EducationalResource .
        ?s <http://purl.org/spar/fabio/hasDiscipline> ?o .
    }
""")


def count_disciplines(graph: rdflib.Graph) -> Counter[str]:
    """Count disciplines."""
    res = list(graph.query(COUNT_DISCIPLINES_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_DISCIPLINES_SPARQL}")
    names = get_discipline_names()
    rv = Counter(
        names[CONVERTER.parse_uri(discipline, strict=True).identifier] for (discipline,) in res
    )
    frv: Counter[str] = Counter()
    for k, v in rv.most_common():
        if v > 1:
            frv[k] = v
        else:
            frv["Other"] += v
    return frv


COUNT_COMMUNITIES_SPARQL = dedent(f"""\
    SELECT ?o
    WHERE {{
        ?s a educor:EducationalResource .
        ?s ?p ?o
        VALUES ?p {{ <{SUPPORTING_COMMUNITY_PRED}> <{RECOMMENDING_COMMUNITY_PRED}> }}
    }}
""")


def count_communities(graph: rdflib.Graph) -> Counter[str]:
    """Count communities."""
    community_labels = get_community_labels()
    res = list(graph.query(COUNT_COMMUNITIES_SPARQL))
    if len(res) == 0:
        raise ValueError(f"query returned no results:\n{COUNT_COMMUNITIES_SPARQL}")
    # TODO this should be in the graph!
    rv = Counter(
        community_labels[str(community).removeprefix("https://id.dalia.education/community/")]
        for (community,) in res
    )
    for k, v in rv.most_common():
        if v < 3:
            rv["Other"] += v
            del rv[k]
    return rv


def barplot_counter(
    counter: Counter[str],
    *,
    ax: matplotlib.axes.Axes | None = None,
    title: str | None = None,
    log: bool = True,
) -> matplotlib.axes.Axes:
    """Plot a counter."""
    categories, counts = zip(*counter.most_common(), strict=False)
    rv = sns.barplot(y=categories, x=counts, ax=ax)
    if title:
        rv.set_title(title)
    if log:
        rv.set_xscale("log")
    return rv


def export_chart(graph: rdflib.Graph, paths: Path | list[Path]) -> None:
    """Export the chart."""
    n_oers = count_oers(graph)
    click.secho(f"DALIA has {n_oers:,} OERs")

    _fig, axes = plt.subplots(3, 3, figsize=(15, 17))

    _combine_language_counter, single_language_counter = count_languages(graph)
    barplot_counter(single_language_counter, ax=axes[0][0], title="Language Occurrence")
    barplot_counter(count_licenses(graph), ax=axes[0][1], title="Licenses")
    barplot_counter(count_file_extensions(graph), ax=axes[0][2], title="File Extensions")
    barplot_counter(count_media_types(graph), ax=axes[1][0], title="Media Types")
    barplot_counter(
        count_proficiency_level(graph, n_oers), ax=axes[1][1], title="Required Proficiency Level"
    )
    barplot_counter(count_disciplines(graph), ax=axes[1][2], title="Disciplines")
    barplot_counter(count_communities(graph), ax=axes[2][0], title="Community")
    barplot_counter(count_target_groups(graph, n_oers), ax=axes[2][1], title="Target Groups")
    barplot_counter(
        count_learning_resource_type(graph), ax=axes[2][2], title="Learning Resource Type"
    )

    plt.tight_layout()
    if not isinstance(paths, list):
        paths = [paths]
    for path in paths:
        plt.savefig(path, dpi=300)
    plt.close()
