# Copyright 2025 West University of Timisoara
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import click
import geopandas as gpd
from geopandas import GeoDataFrame
from owslib.etree import etree
from owslib.wfs import WebFeatureService
from owslib.fes import PropertyIsEqualTo, OgcExpression, FilterRequest

COUNTY_TABLE = "geospatial:ro_judete_poligon"
UAT_TABLE = "geospatial:ro_uat_poligon"


class UAT(object):

    def __init__(
        self,
        endpoint: str = "http://www.geo-spatial.org/geoserver/ows",
        epsg: int = 4326,
    ):
        self._ows_endpoint = endpoint
        self._wfs11 = WebFeatureService(url=self._ows_endpoint, version="1.1.0")
        self._epsg = epsg

    def get_feature(
        self, feature: str, filter: OgcExpression, epsg: int = None
    ) -> GeoDataFrame:
        """Get features matching the filter. Return in specified EPSG

        :param feature: What WFS feature to query
        :param filter: The WFS filter to apply
        :param epsg: the projection the results should be
        :return: Dataframe with the result
        """
        epsg_ = epsg if epsg is not None else self._epsg
        filterxml = etree.tostring(filter.toXML(), encoding="unicode")
        response = self._wfs11.getfeature(
            feature,
            filter=filterxml,
            outputFormat="json",
            srsname=f"urn:x-ogc:def:crs:EPSG:{epsg_}",
        )
        data = gpd.GeoDataFrame.from_file(response)
        if data.empty:
            return None
        # minx, miny, maxx, maxy = data.unary_union.envelope.bounds
        return data  # , (minx, miny, maxx, maxy)


def get_administrative_unit_by_code(siruta_code: int, epsg: int = 4326) -> GeoDataFrame:
    """Query the Geo-Spatial.org service for a Romanian administrative unit by SIRUTA Code

    :param siruta_code: Siruta code of the administrative unit
    :param epsg: The EPSG of the results
    :return: GeoDataFrame with the matches
    """
    uat = UAT(epsg=epsg)
    wfs_filter = PropertyIsEqualTo(propertyname="natcode", literal=f"{siruta_code}")
    return uat.get_feature(UAT_TABLE, filter=wfs_filter)


def get_administrative_unit_by_name(
    name: str, matchcase=False, epsg: int = 4326
) -> GeoDataFrame:
    """Query the Geo-Spatial.org service for a Romanian administrative unit by name

    :param name: Name of the administrative unit
    :param matchcase: Whether to match the case
    :param epsg: The EPSG of the results
    :return: GeoDataFrame with the matches
    """
    uat = UAT(epsg=epsg)
    wfs_filter = PropertyIsEqualTo(
        propertyname="name", literal=f"{name}", matchcase=matchcase
    )
    return uat.get_feature(UAT_TABLE, filter=wfs_filter)


def get_county_by_mnemonic(county_mnemonic: str, epsg: int = 4326) -> GeoDataFrame:
    """Query the Geo-Spatial.org service for a Romanian county

    :param county_mnemonic: Mnemonic code of the county
    :param epsg: The EPSG of the results
    :return: GeoDataFrame with the matches
    """
    uat = UAT(epsg=epsg)
    wfs_filter = PropertyIsEqualTo(
        propertyname="mnemonic", literal=f"{county_mnemonic}"
    )
    return uat.get_feature(COUNTY_TABLE, filter=wfs_filter)


def get_county_by_name(name: str, epsg: int = 4326) -> GeoDataFrame:
    """Query the Geo-Spatial.org service for a Romanian county

    :param name: County Name
    :param epsg: The EPSG of the results
    :return: GeoDataFrame with the matches
    """
    uat = UAT(epsg=epsg)
    wfs_filter = PropertyIsEqualTo(propertyname="name", literal=f"{name}")
    return uat.get_feature(COUNTY_TABLE, filter=wfs_filter)


@click.command("get-county-by-name")
@click.option("--name", prompt="Name of County", help="County Name")
@click.option("--epsg", default=3844, help="EPSG of the results")
@click.option(
    "--output", default=None, help="Destination of the output saved as GeoJSON"
)
def get_county_by_name_cli(name: str, output, epsg: int = 3844):
    """Retrieves the county by name"""
    result = get_county_by_name(name, epsg=epsg)
    if result is None:
        click.echo("County not found")
        return
    if output is not None:
        result.to_file(output, driver="GeoJSON")
    else:
        click.echo(result)
    return result


@click.command("get-county-by-mnemonic")
@click.option("--name", prompt="County Mnemonic", help="County Mnemonic. Eg. TM")
@click.option("--epsg", default=3844, help="EPSG of the results")
@click.option(
    "--output", default=None, help="Destination of the output saved as GeoJSON"
)
def get_county_by_mnemonic_cli(name: str, output: str, epsg: int = 3844):
    """Retrieves the county by mnemonic"""
    result = get_county_by_mnemonic(name, epsg=epsg)
    if result is None:
        click.echo("County not found")
        return
    if output is not None:
        result.to_file(output, driver="GeoJSON")
    else:
        click.echo(result)
    return result


@click.command("get-administrative-unit-by-name")
@click.option(
    "--name", prompt="Name of administrative unit", help="Administrative unit name"
)
@click.option("--epsg", default=3844, help="EPSG of the results")
@click.option(
    "--output", default=None, help="Destination of the output saved as GeoJSON"
)
def get_administrative_unit_by_name_cli(
    name: str, output=str, matchcase=False, epsg: int = 4326
):
    """Retrieves the administrative unit by name"""
    result = get_administrative_unit_by_name(name, epsg=epsg)
    if result is None:
        click.echo("Administrative Unit not found")
        return
    if output is not None:
        result.to_file(output, driver="GeoJSON")
    else:
        click.echo(result)
    return result


@click.command("get-administrative-unit-by-code")
@click.option(
    "--code",
    prompt="Administrative unit code",
    help="SIRUTA Code of the administrative unit",
)
@click.option("--epsg", default=3844, help="EPSG of the results")
@click.option(
    "--output", default=None, help="Destination of the output saved as GeoJSON"
)
def get_administrative_unit_by_code_cli(code: int, output, epsg: int = 4326):
    """Uses the SIRUTA Code to retrieve the administrative unit"""
    result = get_administrative_unit_by_code(siruta_code=code, epsg=epsg)
    if result is None:
        click.echo("Administrative Unit not found")
        return
    if output is not None:
        result.to_file(output, driver="GeoJSON")
    else:
        click.echo(result)
    return result
