import glob
import os
import zipfile
import json
import hashlib
from pathlib import Path

from dotenv import load_dotenv
from pygbif import occurrences, species
import pandas as pd
import requests
from diskcache import Cache

KINGDOM_KEYS = {
    "Animalia": 1,
    "Plantae": 6,
    "Fungi": 5,
    "Bacteria": 3,
    "Archaea": 4,
    "Chromista": 7,
    "Protozoa": 8,
    "Viruses": 9,
}

path_root = Path(__file__).resolve().parent
load_dotenv(path_root / ".env")

# Cache persistente
cache = Cache(path_root / ".gbif_cache")

def make_cache_key(data: dict) -> str:
    """Generate stable hash key for caching."""
    serialized = json.dumps(data, sort_keys=True)
    return hashlib.md5(serialized.encode()).hexdigest()


def clear_cache():
    """Clear all cached data."""
    cache.clear()
    print("Cache cleared.")

def getCountryCode(country_name: str, force_refresh: bool = False):
    """Get country code from REST Countries API with cache."""
    
    cache_key = f"country_code_{country_name.lower()}"

    if cache_key in cache and not force_refresh:
        return cache[cache_key]

    url = f"https://restcountries.com/v3.1/name/{country_name}"
    response = requests.get(url)
    result = response.json()

    code = result[0]['cca2']
    cache[cache_key] = code

    return code


# =============================
# Query Builder
# =============================

def create_query(
    keys: list,
    country: str = None,
    year_range: tuple = None,
    lat_min: float = None,
    lat_max: float = None,
    lon_min: float = None,
    lon_max: float = None
):

    query = {
        "type": "and",
        "predicates": [
            {
                "type": "in",
                "key": "TAXON_KEY",
                "values": keys
            },
            {
                "type": "equals",
                "key": "HAS_COORDINATE",
                "value": True
            }
        ]
    }

    if country:
        countryCode = getCountryCode(country)
        query["predicates"].append({
            "type": "equals",
            "key": "COUNTRY",
            "value": countryCode
        })

    if year_range:
        query["predicates"].append({
            "type": "greaterThanOrEquals",
            "key": "YEAR",
            "value": year_range[0]
        })
        query["predicates"].append({
            "type": "lessThanOrEquals",
            "key": "YEAR",
            "value": year_range[1]
        })

    if None not in (lat_min, lat_max, lon_min, lon_max):
        query["predicates"].append({
            "type": "within",
            "geometry": f"POLYGON(({lon_min} {lat_min}, {lon_min} {lat_max}, {lon_max} {lat_max}, {lon_max} {lat_min}, {lon_min} {lat_min}))"
        })

    return query

def get_occurrences_by_key(
    keys: list,
    country: str = None,
    year_range: tuple = None,
    lat_min: float = None,
    lat_max: float = None,
    lon_min: float = None,
    lon_max: float = None,
    force_refresh: bool = False,
    cache_expire: int = None  # segundos (ex: 86400 = 1 dia)
):

    if not env_key_exists():
        raise Exception(
            "Missing GBIF credentials. "
            "Call save_gbif_credentials(user, email, password)."
        )

    query = create_query(keys, country, year_range, lat_min, lat_max, lon_min, lon_max)
    cache_key = make_cache_key(query)

    # 🔹 Retorna do cache
    if cache_key in cache and not force_refresh:
        print("Returning occurrences from cache...")
        return cache[cache_key]

    print("Downloading occurrences from GBIF...")

    res = occurrences.download(query)

    status = None
    while status != "SUCCEEDED":
        meta = occurrences.download_meta(res[0])
        status = meta["status"]

    directory = "gbif_download"
    os.makedirs(directory, exist_ok=True)

    occurrences.download_get(res[0], path=f"{directory}/")
    zip_original = f"{directory}/{res[0]}.zip"

    with zipfile.ZipFile(zip_original) as zf:
        csv_name = zf.namelist()[0]
        df = pd.read_csv(zf.open(csv_name), sep="\t")

    dataset = df[['scientificName', 'countryCode', 'decimalLatitude',
                  'decimalLongitude', 'day', 'month', 'year']]

    dataset = dataset.drop_duplicates().dropna()

    dataset = dataset.rename(columns={
        'countryCode': 'country',
        'decimalLatitude': 'latitude',
        'decimalLongitude': 'longitude'
    })

    dataset['source'] = 'gbif'

    delete_download_folder()

    # 🔹 Salva no cache (com TTL opcional)
    if cache_expire:
        cache.set(cache_key, dataset, expire=cache_expire)
    else:
        cache[cache_key] = dataset

    return dataset

def get_species_keys(species_names: list):
    result = []
    for name in species_names:
        res = species.name_suggest(q=name, rank="species", limit=10)
        if res:
            result.append(res[0]["key"])
    return result

def save_gbif_credentials(user: str, email: str, password: str):
    env_path = path_root / '.env'
    data = {
        "GBIF_USER": user,
        "GBIF_EMAIL": email,
        "GBIF_PWD": password
    }

    with open(env_path, "w", encoding="utf-8") as f:
        for k, v in data.items():
            f.write(f"{k}={v}\n")

    load_dotenv(path_root / ".env")
    print("GBIF credentials saved.")

def env_key_exists() -> bool:
    keys = {"GBIF_USER", "GBIF_EMAIL", "GBIF_PWD"}
    return all(os.getenv(k) is not None for k in keys)

def delete_gbif_credentials():
    env_path = path_root / '.env'
    if os.path.exists(env_path):
        os.remove(env_path)
        print("GBIF credentials removed.")

def delete_download_folder():
    directory = "gbif_download"
    if os.path.exists(directory):
        files = glob.glob(f"{directory}/*")
        for f in files:
            os.remove(f)
        os.rmdir(directory)


def get_species_autocomplete(name: str, rank: str = None):
    params = {"q": name, "limit": 10}
    if rank:
        params["rank"] = rank.upper()  # FAMILY, GENUS, SPECIES
    return species.name_suggest(**params)

def get_kingdoms():
    """ Kingdoms available in GBIF. """
    roots = []
    for name, key in KINGDOM_KEYS.items():
        info = species.name_usage(key)  # busca detalhes do reino
        roots.append({
            "key": key,
            "name": name,
            "canonicalName": name,
            "rank": "KINGDOM",
            "hasChildren": True,
            "numDescendants": info.get("numDescendants", 0),
        })
    return roots

def get_tree_node(usage_key: int, limit: int = 50, offset: int = 0):
    result = species.name_usage(
        key=usage_key,
        data="children",
        limit=limit,
        offset=offset
    )
    
    children = []
    for c in result.get("results", []):
        children.append({
            "key": c.get("key"),
            "name": c.get("scientificName"),
            "canonicalName": c.get("canonicalName"),
            "rank": c.get("rank"),
            "hasChildren": c.get("numDescendants", 0) > 0,
            "numDescendants": c.get("numDescendants", 0),
        })
    
    return {
        "results": children,
        "endOfRecords": result.get("endOfRecords", True),
        "total": result.get("count", 0),
    }

