import base64
import datetime
import gzip
import json

from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.cache import cache_control
from django.views.decorators.http import require_GET
from psycopg2.sql import SQL

from territories_dashboard_lib.commons.decorators import use_payload
from territories_dashboard_lib.geo_lib.payloads import (
    GeoFeaturesPayload,
    MainTerritoryParams,
    SearchTerritoriesParams,
    TerritoriesParams,
    TerritoryFeaturePayload,
)
from territories_dashboard_lib.indicators_lib.enums import (
    FRANCE_GEOLEVEL_TITLES,
    MeshLevel,
)
from territories_dashboard_lib.indicators_lib.query.commons import (
    get_sub_territories,
)
from territories_dashboard_lib.indicators_lib.query.utils import (
    format_sql_codes,
    run_custom_query,
)
from territories_dashboard_lib.website_lib.conf import get_meshes_for_current_project

from .enums import GeoFeatureType
from .models import GeoFeature


class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return (
                obj.date().isoformat()
                if isinstance(obj, datetime.datetime)
                else obj.isoformat()
            )
        return super().default(obj)


@require_GET
@cache_control(max_age=3600)
@use_payload(GeoFeaturesPayload)
def geo_features_view(request, payload):
    geo_feature = get_object_or_404(GeoFeature, id=payload.feature)
    last = payload.last
    limit = payload.limit

    last_year_query = (
        f"SELECT DISTINCT annee FROM {geo_feature.name} ORDER BY annee DESC"
    )
    years = run_custom_query(last_year_query)
    last_year = years[0].get("annee")

    territories = get_sub_territories(
        submesh=payload.submesh, territory=payload.territory
    )
    territories_ids = [t["code"] for t in territories]
    col_id_name = f"code_{payload.submesh}"
    columns = [el.name for el in geo_feature.items.all()]
    if geo_feature.color_column:
        columns.append(geo_feature.color_column)
    if geo_feature.size_column:
        columns.append(geo_feature.size_column)

    coma = ", " if columns else ""
    select_geo_code = (
        f", geo.{col_id_name} AS geo_code"
        if geo_feature.geo_type == GeoFeatureType.point
        else ""
    )

    if geo_feature.geo_type == GeoFeatureType.point:
        where_clause = f"WHERE geo.{col_id_name} IN ('{"', '".join(territories_ids)}')"
    else:
        where_clause = (
            f"JOIN contours_carto_{payload.territory.mesh}_pe contours ON ST_intersects(geo.geometry, contours.geometry)\n"
            f"WHERE contours.code IN {payload.territory.sql_codes}"
        )
    where_clause += f" AND annee = {last_year}"

    pagination = f"AND order_id > {last or 0} ORDER BY order_id ASC LIMIT {limit}"

    query = (
        f"SELECT ST_asgeojson(geo.geometry) AS geojson, order_id{coma + ', '.join(columns)}\n"
        f"{select_geo_code}\n"
        f"FROM {geo_feature.name} AS geo\n"
        f"{where_clause} {pagination}"
    )

    # Execute the query
    results = run_custom_query(query)

    items = geo_feature.items.all()
    data = []
    for r in results:
        properties = {el.label: r.get(el.name) for el in items}
        if geo_feature.color_column or geo_feature.size_column:
            properties["display"] = {}
        if geo_feature.color_column:
            properties["display"]["color"] = r.get(geo_feature.color_column)
        if geo_feature.size_column:
            properties["display"]["size"] = r.get(geo_feature.size_column)
        d = {
            "type": "Feature",
            "id": r.get("geo_code"),
            "properties": properties,
            "geometry": json.loads(r.get("geojson")),
        }
        data.append(d)

    last_queried_order_id = results[-1]["order_id"] if results else None

    compressed_data = gzip.compress(json.dumps(data, cls=DateTimeEncoder).encode())
    compressed_base64 = base64.b64encode(compressed_data).decode()

    return JsonResponse({"data": compressed_base64, "last": last_queried_order_id})


@require_GET
@cache_control(max_age=3600)
@use_payload(MainTerritoryParams)
def main_territory_view(request, payload):
    codes = ", ".join(f"'{code.strip()}'" for code in payload.geo_id)

    query = f"""
        SELECT code as id, ST_asgeojson(geometry) AS polygon
        FROM contours_carto_{payload.geo_level}_pe
        WHERE code IN ({codes})
        """

    results = run_custom_query(query)

    data = [
        {
            "id": r.get("id"),
            "polygon": json.loads(r.get("polygon")),
        }
        for r in results
    ]

    return JsonResponse(data, safe=False)


@require_GET
@cache_control(max_age=3600)
@use_payload(TerritoriesParams)
def precise_view(request, payload):
    mesh_level = payload.mesh
    territories_ids = payload.territories

    if not mesh_level:
        return JsonResponse({"error": "Missing 'mesh' parameter"}, status=400)

    query = f"""
        SELECT code AS id, ST_asgeojson(geometry) AS polygon
        FROM contours_carto_{mesh_level}
        WHERE code IN ('{"', '".join(territories_ids)}')
        """

    results = run_custom_query(query)

    territories = {r.get("id"): json.loads(r.get("polygon")) for r in results}

    return JsonResponse(territories)


@require_GET
@cache_control(max_age=3600)
@use_payload(TerritoryFeaturePayload)
def territories_view(request, payload):
    if payload.codes:
        territories_ids = payload.codes
    else:
        territories = get_sub_territories(
            submesh=payload.submesh, territory=payload.territory
        )
        territories_ids = [t["code"] for t in territories]
    query = f"""
        SELECT code AS id, ST_asgeojson(geometry) AS polygon
        FROM contours_carto_{payload.submesh}_pe
        WHERE code IN {format_sql_codes(territories_ids)}
        """

    results = run_custom_query(query)

    data = [
        {"type": "Feature", "id": r.get("id"), "geometry": json.loads(r.get("polygon"))}
        for r in results
    ]
    return JsonResponse(data, safe=False)


def _fill_territory_li(code, name, mesh):
    label = name if mesh == MeshLevel.fr else f"{name} - {code}"
    return f"""<li data-code="{code}" data-name="{name}" data-mesh="{mesh}" onclick="chooseTerritory(this)"><button aria-label="Choisir {label}">{label}</button></li>"""


@require_GET
@cache_control(max_age=3600)
@use_payload(SearchTerritoriesParams)
def search_territories_view(request, payload):
    mesh = payload.mesh
    search = payload.search
    offset = payload.offset
    if mesh == MeshLevel.fr:
        lis = []
        for code, name in FRANCE_GEOLEVEL_TITLES.items():
            li = _fill_territory_li(code, name, mesh)
            lis.append(li)
        return HttpResponse("\n".join(lis))
    table_name = f"arbo_{mesh}"
    pagination = 20
    include_ept = (
        mesh == MeshLevel.epci and MeshLevel.ept in get_meshes_for_current_project()
    )
    if include_ept:
        query = SQL(f"""
        SELECT code, name, mesh FROM (
            SELECT DISTINCT ON (code, name) code, name, mesh FROM (
                SELECT code, name, 'epci' as mesh FROM arbo_epci
                WHERE unaccent(name) || code ILIKE unaccent(%s)
                AND code !~ '^[A-Z]{{3}}$'
                UNION ALL
                SELECT code, name, 'ept' as mesh FROM arbo_ept
                WHERE unaccent(name) || code ILIKE unaccent(%s)
                AND code !~ '^[A-Z]{{3}}$'
            ) AS raw
            ORDER BY code, name, mesh
        ) AS combined
        ORDER BY name, code
        LIMIT {pagination} OFFSET {offset * pagination};
        """)
    else:
        query = SQL(f"""
        SELECT DISTINCT code, name, '{mesh}' as mesh FROM {table_name}
        WHERE unaccent(name) || code ILIKE unaccent(%s)
        AND code !~ '^[A-Z]{{3}}$'
        ORDER BY name, code
        LIMIT {pagination} OFFSET {offset * pagination};
        """)
    search_param = f"%{search}%"
    query_params = [search_param]
    if include_ept:
        query_params = [search_param, search_param]
    territories = run_custom_query(query, query_params)
    lis = []
    for territory in territories:
        li = _fill_territory_li(territory["code"], territory["name"], territory["mesh"])
        lis.append(li)
    if len(territories) == pagination:
        li = f"""<li data-type="load"><button onclick="loadMoreTerritories(this)" data-mesh="{mesh}" data-offset="{offset + 1}" class="fr-btn fr-btn--secondary fr-btn--sm">Charger plus de résultats</button></li>"""
        lis.append(li)
    return HttpResponse("\n".join(lis))
