import math

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 base64

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: str) -> (Artefact, Artefact):
    """Create image with the lines laid over."""
    # TODO Don't load image twice
    source_color_image = cv.imread(cv.samples.findFile(filename), cv.IMREAD_COLOR)
    source_greyscale_image = cv.imread(cv.samples.findFile(filename), cv.IMREAD_GRAYSCALE)
    edge_detected_image = cv.Canny(source_greyscale_image, 50, 200, None, 3)
    # edge_detected_image_color_format = cv.cvtColor(edge_detected_image, cv.COLOR_GRAY2BGR)
    detected_lines = cv.HoughLinesP(edge_detected_image, 1, np.pi / 180, 150, None, 250, 0)
    # if detected_lines is not None:
    #     for i in range(0, len(detected_lines)):
    #         l = detected_lines[i][0]
    #         cv.line(edge_detected_image_color_format, (l[0], l[1]), (l[2], l[3]), (0, 0, 255), 6, cv.LINE_AA)
    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)
    # cv.imshow("Probabilistic Line Transform", source_color_image)
    temp_image_loc = Path(__file__).parent.parent.parent / Path("data") / Path("rail_image_annotated.jpg")
    cv.imwrite(str(temp_image_loc), source_color_image)
    with temp_image_loc.open("rb") as image_file:
        data = base64.b64encode(image_file.read()).decode()
    return Artefact("inline", data, "", "base64"), Artefact("inline", f"D: {detected_lines}\nP: {parallel_line_indices}", "", "text/plain")


def rail_finder_algo_two(filename):
    source_greyscale_image = cv.imread(cv.samples.findFile(filename), cv.IMREAD_GRAYSCALE)
    dst = cv.Canny(source_greyscale_image, 50, 200, None, 3)
    cdst = cv.cvtColor(dst, cv.COLOR_GRAY2BGR)
    lines = cv.HoughLines(dst, 1, np.pi / 180, 450, None, 0, 0)
    if lines is not None:
        for i in range(0, len(lines)):
            rho = lines[i][0][0]
            theta = lines[i][0][1]
            a = math.cos(theta)
            b = math.sin(theta)
            x0 = a * rho
            y0 = b * rho
            pt1 = (int(x0 + 1000 * (-b)), int(y0 + 1000 * a))
            pt2 = (int(x0 - 1000 * (-b)), int(y0 - 1000 * a))
            cv.line(cdst, pt1, pt2, (0, 0, 255), 6, cv.LINE_AA)
    cv.imshow("Standard Hough Line", cdst)


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
            body = await request.json()
            control = body.get("ctrl")
            # The predefined activity expects the control to be on the cin port.
            if control not in ["cin"]:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": "This activity expects the control flow input on port 'cin'."})
            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."})
            # rail_image = fill_artefact(rail_image_data)
            # algorithm = fill_artefact(algorithm_data)

            # TODO Error checking
            # rail_image = Image.open(BytesIO(base64.b64decode(rail_image_data.get("content"))))
            temp_image_loc = Path(__file__).parent.parent.parent / Path("data") / Path("rail_image.jpg")
            # rail_image.save(str(temp_image_loc), temp_image_loc.suffix[1:])
            orig = base64.b64decode(rail_image_data.get("content"))
            with temp_image_loc.open("wb") as f_output:
                f_output.write(orig)
            # TODO Actually use the provided code
            annotated_image_artefact, data_artefact = rail_finder_algo_one(str(temp_image_loc))
            # mime = magic.Magic(mime=True).from_file(file_path)
            response = {
                "ctrl": "ok",
                "output": {
                    "image": {
                        "type": annotated_image_artefact.type,
                        "content": annotated_image_artefact.content,
                        "name": annotated_image_artefact.name,
                        "encoding": annotated_image_artefact.encoding
                    },
                    "data": {
                        "type": data_artefact.type,
                        "content": data_artefact.content,
                        "name": data_artefact.name,
                        "encoding": data_artefact.encoding
                    },
                }
            }
            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)



def main():
    filename = str(Path(__file__).parent.parent.parent / Path("tests/octiva") / Path("rails_0.jpg"))
    rail_finder_algo_one(filename)
    # rail_finder_algo_two(filename)
    cv.waitKey()


if __name__ == "__main__":
    main()
