import os
import re
import shutil
import sys
import tarfile
import zipfile
from pathlib import Path

from dev_tools.utility.network import download_check_hash


def cache_solr_release_package(cache_dir: Path):
    version = "8.11.1"
    solr_dir = cache_dir / "solr"
    solr_dir.mkdir(parents=True, exist_ok=True)
    # cache all packages for redundancy
    files = {
        f"solr-{version}.zip": "085f6fce67326ffdcca965854e25f720849bc3e0dc7e4fac5fa0b550d42af188"
        "b10e2b799039a50d00fcc84c364d294572ceb5ecdfa21eb28df331ab881a3689",
        f"solr-{version}.tgz": "4893f836aec84b03d7bfe574e59e305c03b5ede4a48020034fbe81440b8feee7"
        "9e55fd9ead230e5b89b3f25124e9b56c1ddc4bb5c7f631cf4e846b9cab5f9a45",
    }
    for filename, hash_digest in files.items():
        solr_pkg = solr_dir / filename
        if not solr_pkg.exists():
            download_check_hash(
                f"https://dlcdn.apache.org/lucene/solr/{version}/{filename}",
                hash_digest,
                solr_pkg,
            )
    # select the package for this platform
    if sys.platform.startswith("win"):
        filename = f"solr-{version}.zip"
    else:
        filename = f"solr-{version}.tgz"
    solr_pkg = solr_dir / filename
    if not solr_pkg.exists():
        raise Exception("Solr pkg missing in cache")
    return solr_pkg, f"\t{filename} - opti2"


def build_deps_solr(deps_dir: Path, solr_package_path: Path):
    solr_dir = deps_dir / "solr"
    with open(solr_package_path, "rb") as f:
        if solr_package_path.name.endswith(".zip"):
            with zipfile.ZipFile(f) as zf:
                zf.extractall(str(deps_dir))
        elif solr_package_path.name.endswith(".tgz"):
            tf = tarfile.open(mode="r:gz", fileobj=f)
            try:
                tf.extractall(str(deps_dir))
            finally:
                tf.close()
        else:
            assert False
    extracted_dir = deps_dir / solr_package_path.name.rpartition(".")[0]
    extracted_dir.resolve(strict=True)
    extracted_dir.rename(solr_dir)
    # optimize contrib dir by collecting the used jars
    contrib_dir = solr_dir / "contrib_optimized"
    for path, jar_prefix in [
        ("analysis-extras/lib", "icu4j-"),
        ("analysis-extras/lucene-libs", "lucene-analyzers-icu-"),
    ]:
        names = [
            item.name
            for item in (solr_dir / "contrib" / path).iterdir()
            if item.name.startswith(jar_prefix) and item.name.endswith(".jar")
        ]
        if len(names) != 1:
            raise Exception(f"Solr contrib jar was not found: {jar_prefix}")
        os.makedirs(contrib_dir / path, exist_ok=True)
        shutil.copy(solr_dir / "contrib" / path / names[0], contrib_dir / path / names[0])
    # remove unused data and libs
    for name in ["bin", "contrib", "dist", "docs", "example", "server/scripts", "server/solr"]:
        shutil.rmtree(solr_dir / name)
    for name in ["CHANGES.txt", "LUCENE_CHANGES.txt", "README.txt"]:
        os.unlink(solr_dir / name)
    web_inf_lib_dir = solr_dir / "server/solr-webapp/webapp/WEB-INF/lib"
    # Remove not used libraries: curator, netty, all kinds of spatial things, some lucene utils, jwt, kerberos, snappy
    lib_names = [
        ("antlr4-runtime", False),  # 0.302; language detection; we roll our own
        ("apiguardian-api", True),  # 0.002
        ("asm", True),  # 0.122
        ("asm-commons", True),  # 0.072
        ("avatica-core", True),  # 1.299; calcite wire protocol (JSON/protobuf); why is this needed? internal?
        ("caffeine", True),  # 0.925
        ("calcite-core", True),  # 6.13; calcite SQL driver; why is this needed? internal?
        ("calcite-linq4j", True),  # 0.507; calcite SQL driver; why is this needed? internal?
        ("checker-qual", True),  # 0.207
        ("commons-cli", True),  # 0.054
        ("commons-codec", True),  # 0.344
        ("commons-collections", True),  # 0.588
        ("commons-collections4", True),  # 0.752
        ("commons-compiler", True),  # 0.038
        ("commons-configuration2", True),  # 0.617
        ("commons-exec", True),  # 0.054
        ("commons-io", True),  # 0.285
        ("commons-lang", True),  # 0.284
        ("commons-lang3", True),  # 0.523
        ("commons-math3", True),  # 2.214
        ("commons-text", True),  # 0.197
        ("curator-client", False),  # 2.423; high-level Zookeeper client
        ("curator-framework", False),  # 0.202; high-level Zookeeper client
        ("curator-recipes", False),  # 0.284; high-level Zookeeper client
        ("disruptor", True),  # 0.083; Concurrent Programming Framework
        ("eigenbase-properties", True),  # 0.018; Type-safe access to Java system properties.
        ("esri-geometry-api", False),  # 0.931; spatial dta processing
        ("guava", True),  # 2.734
        ("hadoop-annotations", False),  # 0.06
        ("hadoop-auth", False),  # 0.139
        ("hadoop-common", False),  # 4.183
        ("hadoop-hdfs-client", False),  # 5.139
        ("hppc", True),  # 1.159; High Performance Primitive Collections
        ("htrace-core4", False),  # 1.502; tracing in distributed systems
        ("http2-client", True),  # 0.029
        ("http2-common", True),  # 0.206
        ("http2-hpack", True),  # 0.052
        ("http2-http-client-transport", True),  # 0.042
        ("httpclient", True),  # 0.78
        ("httpcore", True),  # 0.328
        ("httpmime", True),  # 0.042
        ("jackson-annotations", True),  # 0.076
        ("jackson-core", True),  # 0.365; JSON streaming processor
        ("jackson-databind", True),  # 1.516
        ("jackson-dataformat-smile", True),  # 0.094
        ("janino", True),  # 0.801; Janino is a super-small, super-fast Java compiler.
        ("java-semver", True),  # 0.047
        ("jetty-alpn-client", False),  # 0.017; Application Layer Protocol Negotiation (maybe for http2?)
        ("jetty-alpn-java-client", False),  # 0.017; Application Layer Protocol Negotiation (maybe for http2?)
        ("jetty-client", True),  # 0.326
        ("jetty-http", True),  # 0.225
        ("jetty-io", True),  # 0.179
        ("jetty-util", True),  # 0.582
        ("jose4j", False),  # 0.26; JWT utility library
        ("json-path", True),  # 0.223; A Java DSL for reading JSON documents.
        ("kerb-core", False),  # 0.227; kerberos
        ("kerb-util", False),  # 0.037; kerberos
        ("kerby-asn1", False),  # 0.102; kerberos
        ("kerby-pkix", False),  # 0.205; kerberos
        ("lucene-analyzers-common", True),  # 1.786
        ("lucene-analyzers-kuromoji", False),  # 4.655; Japanese Morphological Analyzer; no schema support yet
        ("lucene-analyzers-nori", False),  # 7.488; Korean Morphological Analyzer; no schema support yet
        ("lucene-analyzers-phonetic", False),  # 0.027; indexing phonetic signatures (for sounds-alike search)
        ("lucene-backward-codecs", False),  # 0.155
        ("lucene-classification", False),  # 0.068; classification of new documents by already indexed data
        ("lucene-codecs", True),  # 0.53
        ("lucene-core", True),  # 3.611
        ("lucene-expressions", False),  # 0.077; Dynamically computed values by query expression.
        ("lucene-grouping", False),  # 0.098; Collectors for grouping search results.
        ("lucene-highlighter", True),  # 0.21; Highlights search keywords in results.
        ("lucene-join", False),  # 0.153; Index-time and Query-time joins for normalized content.
        ("lucene-memory", False),  # 0.052; Single-document in-memory index implementation.
        ("lucene-misc", True),  # 0.106
        ("lucene-queries", True),  # 0.382
        ("lucene-queryparser", True),  # 0.383
        ("lucene-sandbox", False),  # 0.245; Various third party contributions
        ("lucene-spatial-extras", False),  # 0.241; Geospatial search
        ("lucene-spatial3d", False),  # 0.309; 3D spatial planar geometry APIs
        ("lucene-suggest", True),  # 0.25; Auto-suggest and Spellchecking support
        ("netty-buffer", False),  # 0.302
        ("netty-codec", False),  # 0.337
        ("netty-common", False),  # 0.653
        ("netty-handler", False),  # 0.522
        ("netty-resolver", False),  # 0.037
        ("netty-transport", False),  # 0.479
        ("netty-transport-native-epoll", False),  # 0.14
        ("netty-transport-native-unix-common", False),  # 0.04
        ("opentracing-api", True),  # 0.018
        ("opentracing-noop", True),  # 0.011
        ("opentracing-util", True),  # 0.008
        ("protobuf-java", True),  # 1.661; maybe used by avatica?
        ("re2j", True),  # 0.127; linear time regular expression matching engine
        ("rrd4j", True),  # 0.77; time series data logging and graphing
        ("s2-geometry-library-java", False),  # 0.121; spherical geometry library
        ("snappy-java", False),  # 1.946; we don't use snappy
        ("solr-core", True),  # 6.683
        ("solr-solrj", True),  # 2.392
        ("spatial4j", False),  # 0.205; general purpose spatial / geospatial library
        ("stax2-api", True),  # 0.196; extension to the "STandard Api for Xml processing"
        ("t-digest", False),  # 0.061; some map-reduce/statistics library for distributed processing (spark?)
        ("woodstox-core", True),  # 1.584; Stax XML pull API implementation
        ("zookeeper", True),  # 1.246; base Zk library is necessary, but we don't use it
        ("zookeeper-jute", False),  # 0.25
    ]
    exclude_re = "|".join(n for n, i in lib_names if not i)
    exclude_re = re.compile(rf"^(({exclude_re})-[.v0-9-]+(?:[.-](?:incubating|Final|jre))?\.jar)$")
    include_re = "|".join(n for n, i in lib_names if i)
    include_re = re.compile(rf"^(({include_re})-[.v0-9-]+(?:[.-](?:incubating|Final|jre))?\.jar)$")
    for item in web_inf_lib_dir.iterdir():
        i = include_re.match(item.name) is not None
        e = exclude_re.match(item.name) is not None
        if i is e:
            raise Exception(f"Neither included nor excluded: {item.name} at {item.parent}")
        elif e:
            item.unlink()
    # optimized contrib
    shutil.move(contrib_dir, solr_dir / "contrib")
    return solr_dir
