from json import JSONDecodeError

import cv2 as cv
import numpy as np
from pathlib import Path
from typing import Any
import arklog
from fastapi import APIRouter, Query, Request, Response
from fastapi.responses import JSONResponse
import requests
from mocka.artefact import Artefact


def find_parallel(lines) -> []:
    """Find parallel lines and return their index."""
    parallel_lines = []
    # TODO Can probably be done using cross-product/zip
    for i in range(len(lines)):
        for j in range(len(lines)):
            if i == j:
                # Every line is parallel to itself. We don't want all lines...
                continue
            if abs(lines[i][0][1] - lines[j][0][1]) == 0:
                parallel_lines.append(i)
    return parallel_lines


def rail_finder_algo_one(filename: Path, storage_base_uri: str) -> (Artefact, Artefact):
    """Create image with the lines laid over."""
    source_color_image = cv.imread(cv.samples.findFile(str(filename)), cv.IMREAD_COLOR)
    source_greyscale_image = cv.cvtColor(source_color_image, cv.COLOR_BGR2GRAY)
    edge_detected_image = cv.Canny(source_greyscale_image, 50, 200, None, 3)
    detected_lines = cv.HoughLinesP(edge_detected_image, 1, np.pi / 180, 150, None, 250, 0)
    parallel_line_indices = find_parallel(detected_lines)
    # Color found lines red
    for detected_line in detected_lines:
        x1, y1, x2, y2 = detected_line[0]
        cv.line(source_color_image, (x1, y1), (x2, y2), (0, 0, 255), 6, cv.LINE_AA)
    # Color parallel lines green
    for parallel_line_index in parallel_line_indices:
        x1, y1, x2, y2 = detected_lines[parallel_line_index][0]
        cv.line(source_color_image, (x1, y1), (x2, y2), (0, 255, 0), 6, cv.LINE_AA)
    # TODO Replace with in memory version
    temp_image_loc = Path(__file__).parent.parent.parent / Path("data") / Path("rail_image_annotated.jpg")
    cv.imwrite(str(temp_image_loc), source_color_image)
    r = requests.put(f"{storage_base_uri}/files/file/{temp_image_loc.name}", data=temp_image_loc.read_bytes())
    return (
        Artefact("reference", f"{storage_base_uri}/files/file/{temp_image_loc.name}",f"{filename.stem}_annotated{filename.suffix}" , "image/jpg"),
        Artefact("inline", f"D: {detected_lines}\nP: {parallel_line_indices}", f"{filename.stem}_annotated.txt", "text/plain")
    )


class OctivaRouter(APIRouter):
    """"""

    def __init__(self, *args: Any, title: str, description: str, version: str, configuration, **kwargs: Any):
        self.title = title
        self.description = description
        self.version = version
        self.configuration = configuration
        super().__init__(*args, **kwargs)
        arklog.warning("DO NOT DEPLOY IN PRODUCTION")
        arklog.warning("ANY SECURE ENV IS COMPROMISED")
        arklog.warning("WE USE EVAL TO RUN ARBITRARY USER PROVIDED STRINGS")

        @self.get("/")
        async def root_mock(request: Request, query: str | None = Query(None)) -> Response:
            """"""
            # TODO Change some of this to use buffers instead of files
            try:
                body = await request.json()
            except JSONDecodeError:
                return JSONResponse(status_code=500, content={"ctrl": "error", "debug": "Could not parse json input."})
            control = body.get("ctrl")
            # The predefined activity expects the control to be on the cin or rep port.
            if control not in ["cin", "rep"]:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": "This activity expects the control flow input on port 'cin' or 'rep'."})
            body_artefacts = body.get("input", {})
            rail_image_data = body_artefacts.get("image")
            algorithm_data = body_artefacts.get("algorithm")
            if not rail_image_data or not algorithm_data:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": "Input artefacts are missing."})
            if rail_image_data.get("type") not in ["reference"]:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": "Only reference image file types are implemented at the moment."})
            # TODO Do this in memory
            try:
                image_response = requests.get(rail_image_data.get("content"), allow_redirects=True)
            except ConnectionError as e:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": f"Could not connect to the file storage backend. {e}"})
            if image_response.status_code != 200:
                arklog.warning(f"{image_response.status_code=} File was likely not found on the storage backend.")
                return JSONResponse(status_code=500, content={"ctrl": "error", "debug": f"File not found on storage backend."})
            temp_image_loc = Path(__file__).parent.parent.parent / Path("data") / Path("rail_image.jpg")
            temp_image_loc.write_bytes(image_response.content)
            storage_base_uri = self.configuration.octiva.storage_base_uri
            try:
                annotated_image_artefact, data_artefact = rail_finder_algo_one(temp_image_loc, storage_base_uri)
            except ConnectionError as e:
                arklog.exception(e)
                return JSONResponse(status_code=500, content={"ctrl": "error", "debug": f"Could not connect to endpoint ('{storage_base_uri}'). {e}"})

            response = {
                "ctrl": "ok",
                "output": {
                    "image": annotated_image_artefact.as_dict(),
                    "data": data_artefact.as_dict()
                }
            }
            return JSONResponse(status_code=200, content=response)

        @self.put("/")
        async def root_put_mock(request: Request, query: str | None = Query(None)) -> Response:
            return await root_mock(request, query)

        @self.post("/")
        async def root_post_mock(request: Request, query: str | None = Query(None)) -> Response:
            return await root_mock(request, query)
