"""
Async querying operations
"""
import asyncio
import itertools
import logging

import aiohttp
import aiohttp.client_exceptions
from shapely.ops import transform
import src.modification as mod
import src.constants as c
import src.async_geometry as g_m
import src.geometry as gm


async def get_ky_address_main(dct_lst: list) -> list:
    """ Async cadastre units address search main

    :param dct_lst: search dictionary list to be used
    :return: flattened result
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(get_ky_address(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    result_joined = list(itertools.chain.from_iterable(result))  # Flattening the output
    return result_joined


async def get_ky_address(session: aiohttp.client.ClientSession, dct: dict) -> list:
    """ Async cadastre units address search url

    :param session: aiohttp session
    :param dct: dictionary to used
    :return:
    """
    url = f"{c.GAZETTEER_URL}?address={dct[c.ky_main]}&features={c.ADR_CU}&ihist=0"  # URL to be called

    async with session.get(url) as response:
        result = await response.json()
        modified_dict = mod.ky_adr_response(result, dct)
        return modified_dict


async def determine_building_main(dct_lst: list):
    """Async cadastre units' building main

    :param dct_lst: dictionary to be used
    :return: cadastre units w/ buildings and without
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(determine_building_request(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def determine_building_request(session: aiohttp.client.ClientSession, dct: dict):
    """ Async requesting buildings

    :param session: aiohttp session
    :param dct: dictionary to used
    :return: modified dictionary
    """
    url = f"{c.GAZETTEER_URL}?address={dct[c.adr_srch]}&features={c.ADR_BUILDING}&ihist=0&results=100"  # URL to be called

    async with session.get(url) as response:
        result = await response.json()
        modified_dict = mod.building_adr_response(result, dct)  # modifying response
        return modified_dict


async def get_ky_secondary_geom_main(dct_lst: list):
    """ Async searching secondary choice for cadaste units

    :param dct_lst: dictionary to be used
    :return:
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(get_ky_secondary_geom_request(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def get_ky_secondary_geom_request(session: aiohttp.client.ClientSession, dct: dict, bbox_buffer: int = 15,
                                        chunk_max: int = 6000) -> dict:
    """Async searching secondary choice for cadaste units requesting params

    :param session: aiohttp session
    :param dct: dictionary to used
    :param bbox_buffer: bounding box buffer around intersection
    :param chunk_max: url length max size, starting geometry chunking
    :return:
    """
    # Requesting parameters
    cu_poly_flip = transform(lambda x, y: (y, x), dct[c.kataster_geom])  # flipping cadastre geometry
    cql_params = f"tyyp NOT IN (70, 80) AND DWITHIN(shape, {cu_poly_flip},{bbox_buffer},meters)"  # cql parameters to use
    url = f"{c.ENVIR_URL}?typename={c.ETAK_NAMESPACE}:{c.ETAK_ROAD}&service={c.WFS}&SRS:{c.SRS_3301}&request={c.REQ_TYPE}&outputformat={c.JSON}&cql_filter={cql_params}"
    road_query_has_responded = False  # needed to catch aiohttp error which in some cases may occur

    # Checking for constructed url length and chunking geometry if necessary
    if len(url) >= chunk_max:
        logging.warning(f"URL for {dct[c.ky_main]} exceeded maximum length. Will need to chunk geometry.")
        cadastre_geom_chunks = gm.katana(cu_poly_flip, 225)
        all_road_responses = []  # Collecting all of the responses
        for cadastre_geom in cadastre_geom_chunks:
            # Requesting parameters (slight modification)
            cql_params = f"tyyp NOT IN (70, 80) AND DWITHIN(shape, {cadastre_geom},{bbox_buffer},meters)"  # cql parameters to use
            url = f"{c.ENVIR_URL}?typename={c.ETAK_NAMESPACE}:{c.ETAK_ROAD}&service={c.WFS}&SRS:{c.SRS_3301}&request={c.REQ_TYPE}&outputformat={c.JSON}&cql_filter={cql_params}"

            while road_query_has_responded is False:

                try:
                    async with session.get(url) as response_2:
                        logging.debug(response_2.status)
                        result = await response_2.json()
                        all_road_responses.append(result)
                    modified_result = g_m.check_roads_many(all_road_responses, dct, cu_poly_flip)
                    return modified_result

                except aiohttp.client_exceptions.ContentTypeError as cte:
                    logging.warning(cte)

    # Geometries that will not need to be chunked
    while road_query_has_responded is False:

        try:
            async with session.get(url) as response:
                logging.debug(response.status)
                result = await response.json()
                modified_result = g_m.check_roads(result, dct, cu_poly_flip)
                logging.debug(f"Did not need to chunk cadastre geometry {dct[c.ky_main]}")
                return modified_result

        except aiohttp.client_exceptions.ContentTypeError as cte:
            logging.warning(cte)


async def get_z_main(dct_in: dict):
    """ Async main Z search from input dictionary

    :param dct_in: dictionary to be used for query
    :return:
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)  # 25 seems to be reasonable
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for k_1, v_1 in dct_in.items():
            task = asyncio.ensure_future(get_z_request(session, v_1))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    logging.info("Done w/ Z query")
    return result


async def get_z_request(session: aiohttp.client.ClientSession, dct: dict) -> dict:
    """ Making a Z request

    :param session: aiohttp session to be used
    :param dct: dictionary to used
    :return: modified dictionary
    """
    mapxy_params = f"{dct[c.Y]}%20{dct[c.X]}"
    url = f"{c.X_GIS_URL_DEM}?mode=query&mapxy={mapxy_params}"  # URL to be called

    async with session.get(url) as response:
        result = await response.json(content_type='text/html', encoding="utf-8")
        dct_in = {c.Z: round(result, 2)}
        dct |= dct_in
        return dct


async def get_korteriomand_xy_main(potential_apartment: list):
    """ Main korteriomand first match

    :param potential_apartment: list of potential aparments to match with
    :return: results w/ match where errors are also tagged
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)  # 25 seems to be reasonable
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for apartment in potential_apartment:
            task = asyncio.create_task(get_korteriomand_xy_request(session, apartment))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def get_korteriomand_xy_request(session: aiohttp.client.ClientSession, apartment_tunnus: str) -> dict:
    """ Korteriomand first request

    :param session: session to be used
    :param apartment_tunnus: korteriomand registritunnus
    :return: modified dictionary
    """
    url = f"{c.ADR_KORTERIOMANDID_URL}?typename={c.KY_NAMESPACE}:{c.ADS_KORTERIOMANDID}&service={c.WFS}&request={c.REQ_TYPE}&outputformat={c.JSON}&cql_filter=id_registris={apartment_tunnus}"
    async with session.get(url) as response:
        result = await response.json()
        korteriomand_dct = g_m.korteriomand_response(result, apartment_tunnus)
        return korteriomand_dct


async def get_ky_from_point_main(valid_apartments: list):
    """ Getting the cadastre unit w/ the geometry from korteriomandid main

    :param valid_apartments: valid apartments that were found
    :return: ky added to dicts
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for apartment in valid_apartments:
            task = asyncio.create_task(get_ky_from_point_request(session, apartment))
            tasks.append(task)

        result = await asyncio.gather(*tasks)
    return result


async def get_ky_from_point_request(session: aiohttp.client.ClientSession, valid_apartment_dct: dict) -> dict:
    """ Getting the cadastre unit w/ the geometry from korteriomandid request

    :param session: session to be used
    :param valid_apartment_dct: dictionary w/ valid potential apartment
    :return: modified dictionary
    """
    # Creating a point geometry contains search
    url = f"{c.ENVIR_URL}?typename={c.KY_NAMESPACE}:{c.KY_FEATURETYPE}&service={c.WFS}&request={c.REQ_TYPE}&outputformat={c.JSON}&propertyName={c.KY_TUNNUS}&cql_filter=CONTAINS({c.GEOM},{valid_apartment_dct[c.korteriomand_viitepunkt]})"

    async with session.get(url) as response:
        result = await response.json()
        korteriomand_dct = g_m.ky_korteriomand_response(result, valid_apartment_dct)
        return korteriomand_dct


async def get_ky_address_main_2(dct_lst: list):
    """ Async cadastre units address search main

    :param dct_lst: search dictionary list to be used
    :return: added post index to korteriomandid dictionary
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(get_ky_address_2(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def get_ky_address_2(session: aiohttp.client.ClientSession, dct: dict) -> dict:
    """ Async cadastre units address search url request

    :param session: aiohttp session
    :param dct: dictionary to used
    :return: added post index to korteriomandid dictionary
    """
    url = f"{c.GAZETTEER_URL}?address={dct[c.ky_main]}&features={c.ADR_CU}&ihist=0"  # URL to be called

    async with session.get(url) as response:
        result = await response.json()
        updated_korteriomandid_w_pidx = mod.ky_adr_response_2(result, dct)
        return updated_korteriomandid_w_pidx

"""
Async querying operations
"""
import asyncio
import itertools
import logging

import aiohttp
import aiohttp.client_exceptions
from shapely.ops import transform
import src.modification as mod
import src.constants as c
import src.async_geometry as g_m
import src.geometry as gm


async def get_ky_address_main(dct_lst: list) -> list:
    """ Async cadastre units address search main

    :param dct_lst: search dictionary list to be used
    :return: flattened result
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(get_ky_address(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    result_joined = list(itertools.chain.from_iterable(result))  # Flattening the output
    return result_joined


async def get_ky_address(session: aiohttp.client.ClientSession, dct: dict) -> list:
    """ Async cadastre units address search url

    :param session: aiohttp session
    :param dct: dictionary to used
    :return:
    """
    url = f"{c.GAZETTEER_URL}?address={dct[c.ky_main]}&features={c.ADR_CU}&ihist=0"  # URL to be called

    async with session.get(url) as response:
        result = await response.json()
        modified_dict = mod.ky_adr_response(result, dct)
        return modified_dict


async def determine_building_main(dct_lst: list):
    """Async cadastre units' building main

    :param dct_lst: dictionary to be used
    :return: cadastre units w/ buildings and without
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(determine_building_request(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def determine_building_request(session: aiohttp.client.ClientSession, dct: dict):
    """ Async requesting buildings

    :param session: aiohttp session
    :param dct: dictionary to used
    :return: modified dictionary
    """
    url = f"{c.GAZETTEER_URL}?address={dct[c.adr_srch]}&features={c.ADR_BUILDING}&ihist=0&results=100"  # URL to be called

    async with session.get(url) as response:
        result = await response.json()
        modified_dict = mod.building_adr_response(result, dct)  # modifying response
        return modified_dict


async def get_ky_secondary_geom_main(dct_lst: list):
    """ Async searching secondary choice for cadaste units

    :param dct_lst: dictionary to be used
    :return:
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(get_ky_secondary_geom_request(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def get_ky_secondary_geom_request(session: aiohttp.client.ClientSession, dct: dict, bbox_buffer: int = 15,
                                        chunk_max: int = 6000) -> dict:
    """Async searching secondary choice for cadaste units requesting params

    :param session: aiohttp session
    :param dct: dictionary to used
    :param bbox_buffer: bounding box buffer around intersection
    :param chunk_max: url length max size, starting geometry chunking
    :return:
    """
    # Requesting parameters
    cu_poly_flip = transform(lambda x, y: (y, x), dct[c.kataster_geom])  # flipping cadastre geometry
    cql_params = f"tyyp NOT IN (70, 80) AND DWITHIN(shape, {cu_poly_flip},{bbox_buffer},meters)"  # cql parameters to use
    url = f"{c.ENVIR_URL}?typename={c.ETAK_NAMESPACE}:{c.ETAK_ROAD}&service={c.WFS}&SRS:{c.SRS_3301}&request={c.REQ_TYPE}&outputformat={c.JSON}&cql_filter={cql_params}"
    road_query_has_responded = False  # needed to catch aiohttp error which in some cases may occur

    # Checking for constructed url length and chunking geometry if necessary
    if len(url) >= chunk_max:
        logging.warning(f"URL for {dct[c.ky_main]} exceeded maximum length. Will need to chunk geometry.")
        cadastre_geom_chunks = gm.katana(cu_poly_flip, 225)
        all_road_responses = []  # Collecting all of the responses
        for cadastre_geom in cadastre_geom_chunks:
            # Requesting parameters (slight modification)
            cql_params = f"tyyp NOT IN (70, 80) AND DWITHIN(shape, {cadastre_geom},{bbox_buffer},meters)"  # cql parameters to use
            url = f"{c.ENVIR_URL}?typename={c.ETAK_NAMESPACE}:{c.ETAK_ROAD}&service={c.WFS}&SRS:{c.SRS_3301}&request={c.REQ_TYPE}&outputformat={c.JSON}&cql_filter={cql_params}"

            while road_query_has_responded is False:

                try:
                    async with session.get(url) as response_2:
                        logging.debug(response_2.status)
                        result = await response_2.json()
                        all_road_responses.append(result)
                    modified_result = g_m.check_roads_many(all_road_responses, dct, cu_poly_flip)
                    return modified_result

                except aiohttp.client_exceptions.ContentTypeError as cte:
                    logging.warning(cte)

    # Geometries that will not need to be chunked
    while road_query_has_responded is False:

        try:
            async with session.get(url) as response:
                logging.debug(response.status)
                result = await response.json()
                modified_result = g_m.check_roads(result, dct, cu_poly_flip)
                logging.debug(f"Did not need to chunk cadastre geometry {dct[c.ky_main]}")
                return modified_result

        except aiohttp.client_exceptions.ContentTypeError as cte:
            logging.warning(cte)


async def get_z_main(dct_in: dict):
    """ Async main Z search from input dictionary

    :param dct_in: dictionary to be used for query
    :return:
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)  # 25 seems to be reasonable
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for k_1, v_1 in dct_in.items():
            task = asyncio.ensure_future(get_z_request(session, v_1))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    logging.info("Done w/ Z query")
    return result


async def get_z_request(session: aiohttp.client.ClientSession, dct: dict) -> dict:
    """ Making a Z request

    :param session: aiohttp session to be used
    :param dct: dictionary to used
    :return: modified dictionary
    """
    mapxy_params = f"{dct[c.Y]}%20{dct[c.X]}"
    url = f"{c.X_GIS_URL_DEM}?mode=query&mapxy={mapxy_params}"  # URL to be called

    async with session.get(url) as response:
        result = await response.json(content_type='text/html', encoding="utf-8")
        dct_in = {c.Z: round(result, 2)}
        dct |= dct_in
        return dct


async def get_korteriomand_xy_main(potential_apartment: list):
    """ Main korteriomand first match

    :param potential_apartment: list of potential aparments to match with
    :return: results w/ match where errors are also tagged
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)  # 25 seems to be reasonable
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for apartment in potential_apartment:
            task = asyncio.create_task(get_korteriomand_xy_request(session, apartment))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def get_korteriomand_xy_request(session: aiohttp.client.ClientSession, apartment_tunnus: str) -> dict:
    """ Korteriomand first request

    :param session: session to be used
    :param apartment_tunnus: korteriomand registritunnus
    :return: modified dictionary
    """
    url = f"{c.ADR_KORTERIOMANDID_URL}?typename={c.KY_NAMESPACE}:{c.ADS_KORTERIOMANDID}&service={c.WFS}&request={c.REQ_TYPE}&outputformat={c.JSON}&cql_filter=id_registris={apartment_tunnus}"
    async with session.get(url) as response:
        result = await response.json()
        korteriomand_dct = g_m.korteriomand_response(result, apartment_tunnus)
        return korteriomand_dct


async def get_ky_from_point_main(valid_apartments: list):
    """ Getting the cadastre unit w/ the geometry from korteriomandid main

    :param valid_apartments: valid apartments that were found
    :return: ky added to dicts
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for apartment in valid_apartments:
            task = asyncio.create_task(get_ky_from_point_request(session, apartment))
            tasks.append(task)

        result = await asyncio.gather(*tasks)
    return result


async def get_ky_from_point_request(session: aiohttp.client.ClientSession, valid_apartment_dct: dict) -> dict:
    """ Getting the cadastre unit w/ the geometry from korteriomandid request

    :param session: session to be used
    :param valid_apartment_dct: dictionary w/ valid potential apartment
    :return: modified dictionary
    """
    # Creating a point geometry contains search
    url = f"{c.ENVIR_URL}?typename={c.KY_NAMESPACE}:{c.KY_FEATURETYPE}&service={c.WFS}&request={c.REQ_TYPE}&outputformat={c.JSON}&propertyName={c.KY_TUNNUS}&cql_filter=CONTAINS({c.GEOM},{valid_apartment_dct[c.korteriomand_viitepunkt]})"

    async with session.get(url) as response:
        result = await response.json()
        korteriomand_dct = g_m.ky_korteriomand_response(result, valid_apartment_dct)
        return korteriomand_dct


async def get_ky_address_main_2(dct_lst: list):
    """ Async cadastre units address search main

    :param dct_lst: search dictionary list to be used
    :return: added post index to korteriomandid dictionary
    """
    connector = aiohttp.TCPConnector(limit=c.MAX_CONNECTIONS)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for dct in dct_lst:
            task = asyncio.create_task(get_ky_address_2(session, dct))
            tasks.append(task)

        result = await asyncio.gather(*tasks)

    return result


async def get_ky_address_2(session: aiohttp.client.ClientSession, dct: dict) -> dict:
    """ Async cadastre units address search url request

    :param session: aiohttp session
    :param dct: dictionary to used
    :return: added post index to korteriomandid dictionary
    """
    url = f"{c.GAZETTEER_URL}?address={dct[c.ky_main]}&features={c.ADR_CU}&ihist=0"  # URL to be called

    async with session.get(url) as response:
        result = await response.json()
        updated_korteriomandid_w_pidx = mod.ky_adr_response_2(result, dct)
        return updated_korteriomandid_w_pidx

Async querying operations Async cadastre units address search main      :param dct_lst: search dictionary list to be used     :return: flattened result # Flattening the output Async cadastre units address search url      :param session: aiohttp session     :param dct: dictionary to used     :return: # URL to be called Async cadastre units' building main      :param dct_lst: dictionary to be used     :return: cadastre units w/ buildings and without Async requesting buildings      :param session: aiohttp session     :param dct: dictionary to used     :return: modified dictionary # URL to be called # modifying response Async searching secondary choice for cadaste units      :param dct_lst: dictionary to be used     :return: Async searching secondary choice for cadaste units requesting params      :param session: aiohttp session     :param dct: dictionary to used     :param bbox_buffer: bounding box buffer around intersection     :param chunk_max: url length max size, starting geometry chunking     :return: # Requesting parameters # flipping cadastre geometry # cql parameters to use # needed to catch aiohttp error which in some cases may occur # Checking for constructed url length and chunking geometry if necessary # Collecting all of the responses # Requesting parameters (slight modification) # cql parameters to use # Geometries that will not need to be chunked Async main Z search from input dictionary      :param dct_in: dictionary to be used for query     :return: # 25 seems to be reasonable Making a Z request      :param session: aiohttp session to be used     :param dct: dictionary to used     :return: modified dictionary # URL to be called Main korteriomand first match      :param potential_apartment: list of potential aparments to match with     :return: results w/ match where errors are also tagged # 25 seems to be reasonable Korteriomand first request      :param session: session to be used     :param apartment_tunnus: korteriomand registritunnus     :return: modified dictionary Getting the cadastre unit w/ the geometry from korteriomandid main      :param valid_apartments: valid apartments that were found     :return: ky added to dicts Getting the cadastre unit w/ the geometry from korteriomandid request      :param session: session to be used     :param valid_apartment_dct: dictionary w/ valid potential apartment     :return: modified dictionary # Creating a point geometry contains search Async cadastre units address search main      :param dct_lst: search dictionary list to be used     :return: added post index to korteriomandid dictionary Async cadastre units address search url request      :param session: aiohttp session     :param dct: dictionary to used     :return: added post index to korteriomandid dictionary # URL to be called