import json
import os
import sys
import traceback
from pathlib import Path

import cv2
import numpy as np

repo = Path.home() / 'TibiaPilotNG'
sys.path.insert(0, str(repo))

from src.utils.dpi import enablePerMonitorAwareness

enablePerMonitorAwareness()

import win32gui

from src.utils.display import getSelectedDisplay

display = getSelectedDisplay()
os.environ['TIBIAPILOT_CAPTURE_X'] = str(display['x'])
os.environ['TIBIAPILOT_CAPTURE_Y'] = str(display['y'])
os.environ['TIBIAPILOT_CAPTURE_WIDTH'] = str(display['width'])
os.environ['TIBIAPILOT_CAPTURE_HEIGHT'] = str(display['height'])
os.environ['TIBIAPILOT_OUTPUT_IDX'] = str(display['outputIndex'])

from src.repositories.gameWindow.config import gameWindowSizes, images as game_images
from src.repositories.gameWindow.core import getCoordinate as get_game_window
from src.repositories.radar.config import floorsImgs, floorsLevelsImgs, images as radar_images, pixelsColorsValues
from src.repositories.radar.core import getCoordinate, getFloorLevel
from src.repositories.radar.extractors import getRadarImage
from src.repositories.radar.locators import getRadarToolsPosition
from src.utils.core import getScreenshot

result = {'windows': []}


def collect_windows(hwnd, _):
    if not win32gui.IsWindowVisible(hwnd):
        return
    title = win32gui.GetWindowText(hwnd)
    if title:
        result['windows'].append({
            'title': title,
            'class': win32gui.GetClassName(hwnd),
            'rect': win32gui.GetWindowRect(hwnd),
        })


try:
    win32gui.EnumWindows(collect_windows, None)
    screenshot = getScreenshot()
    result['screenshot'] = None if screenshot is None else list(screenshot.shape)
    if screenshot is not None:
        cv2.imwrite(str(repo / '.runtime-diagnostic.png'), screenshot)
        result['radarTools'] = getRadarToolsPosition(screenshot)
        result['floor'] = getFloorLevel(screenshot)
        result['coordinate'] = getCoordinate(screenshot)
        result['coordinateFromPrevious'] = getCoordinate(
            screenshot, previousCoordinate=(32367, 32225, 7),
        )
        result['gameWindow'] = get_game_window(screenshot, gameWindowSizes[1080])
        templates = {'radarTools': radar_images['tools'], **game_images['arrows']}
        result['templateMatches'] = {}
        for name, template in templates.items():
            match = cv2.matchTemplate(screenshot, template, cv2.TM_CCOEFF_NORMED)
            _, score, _, position = cv2.minMaxLoc(match)
            result['templateMatches'][name] = {
                'score': round(score, 4),
                'position': position,
                'size': [template.shape[1], template.shape[0]],
            }
        radar_match = cv2.matchTemplate(
            screenshot, radar_images['tools'], cv2.TM_CCOEFF_NORMED,
        )
        _, _, _, radar_position = cv2.minMaxLoc(radar_match)
        floor_strip = screenshot[
            radar_position[1] - 7:radar_position[1] + 60,
            radar_position[0] + 28:radar_position[0] + 30,
        ]
        result['floorScores'] = [
            round(float(cv2.matchTemplate(
                floor_strip, template, cv2.TM_CCOEFF_NORMED,
            )[0, 0]), 4)
            for template in floorsLevelsImgs
        ]
        radar_image = getRadarImage(screenshot, (*radar_position, 20, 60))
        cv2.imwrite(str(repo / '.runtime-radar.png'), radar_image)
        result['radarValues'] = np.unique(radar_image).tolist()
        floor_match = cv2.matchTemplate(
            floorsImgs[7], radar_image, cv2.TM_CCOEFF_NORMED,
        )
        _, floor_score, _, floor_position = cv2.minMaxLoc(floor_match)
        cv2.imwrite(
            str(repo / '.runtime-radar-match.png'),
            floorsImgs[7][
                floor_position[1]:floor_position[1] + radar_image.shape[0],
                floor_position[0]:floor_position[0] + radar_image.shape[1],
            ],
        )
        result['radarMapMatch'] = {
            'score': round(floor_score, 4),
            'position': floor_position,
            'shape': list(radar_image.shape),
        }
        profiles = json.loads((repo / 'file.json').read_text(encoding='utf-8'))
        profiles = profiles.get('_default', {}).values()
        profile = next((item for item in profiles if item.get('enabled')), None)
        route = profile['config']['ng_cave']['waypoints']['items'] if profile else []
        result['routeCoordinate'] = getCoordinate(screenshot, waypoints=route)
        route_coordinates = [item['coordinate'] for item in route]
        result['routeMatches'] = []
        for route_floor in sorted({item[2] for item in route_coordinates}):
            candidates = [item for item in route_coordinates if item[2] == route_floor]
            x0 = max(min(item[0] for item in candidates) - 31744 - 150, 0)
            y0 = max(min(item[1] for item in candidates) - 30976 - 150, 0)
            x1 = min(max(item[0] for item in candidates) - 31744 + 150, 2560)
            y1 = min(max(item[1] for item in candidates) - 30976 + 150, 2048)
            area = floorsImgs[route_floor][y0:y1, x0:x1]
            match = cv2.matchTemplate(area, radar_image, cv2.TM_CCOEFF_NORMED)
            _, score, _, position = cv2.minMaxLoc(match)
            result['routeMatches'].append({
                'floor': route_floor,
                'score': round(score, 4),
                'coordinate': [
                    x0 + position[0] + 53 + 31744,
                    y0 + position[1] + 54 + 30976,
                    route_floor,
                ],
            })
        result['radarScaleMatches'] = []
        palette = np.array(sorted(set(pixelsColorsValues.values())), dtype=np.int16)
        quantized_radar = palette[
            np.abs(radar_image[:, :, None].astype(np.int16) - palette).argmin(axis=2)
        ].astype(np.uint8)
        local_scaled = cv2.resize(
            quantized_radar, None, fx=0.25, fy=0.25,
            interpolation=cv2.INTER_NEAREST,
        )
        previous_pixel = (32367 - 31744, 32225 - 30976)
        local_x0 = previous_pixel[0] - local_scaled.shape[1] // 2 - 5
        local_y0 = previous_pixel[1] - local_scaled.shape[0] // 2 - 5
        local_area = floorsImgs[7][
            local_y0:local_y0 + local_scaled.shape[0] + 10,
            local_x0:local_x0 + local_scaled.shape[1] + 10,
        ]
        local_match = cv2.matchTemplate(
            local_area, local_scaled, cv2.TM_CCOEFF_NORMED,
        )
        _, local_score, _, local_position = cv2.minMaxLoc(local_match)
        result['localQuantizedMatch'] = {
            'score': round(local_score, 4),
            'coordinate': [
                local_x0 + local_position[0] + local_scaled.shape[1] // 2 + 31744,
                local_y0 + local_position[1] + local_scaled.shape[0] // 2 + 30976,
                7,
            ],
        }
        local_mask = (local_scaled > 0).astype(np.uint8) * 255
        local_masked_match = cv2.matchTemplate(
            local_area, local_scaled, cv2.TM_CCORR_NORMED,
            mask=local_mask,
        )
        _, masked_score, _, masked_position = cv2.minMaxLoc(local_masked_match)
        result['localMaskedMatch'] = {
            'score': round(masked_score, 4),
            'coordinate': [
                local_x0 + masked_position[0] + local_scaled.shape[1] // 2 + 31744,
                local_y0 + masked_position[1] + local_scaled.shape[0] // 2 + 30976,
                7,
            ],
        }
        for scale in (0.2, 0.25, 0.3, 0.4, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0):
            scaled = cv2.resize(
                radar_image, None, fx=scale, fy=scale,
                interpolation=cv2.INTER_NEAREST,
            )
            match = cv2.matchTemplate(
                floorsImgs[7], scaled, cv2.TM_CCOEFF_NORMED,
            )
            _, score, _, position = cv2.minMaxLoc(match)
            result['radarScaleMatches'].append({
                'scale': scale,
                'score': round(score, 4),
                'position': position,
            })
        result['quantizedRadarScaleMatches'] = []
        for scale in (0.2, 0.25, 0.3, 0.4, 0.5, 0.75, 1.0):
            scaled = cv2.resize(
                quantized_radar, None, fx=scale, fy=scale,
                interpolation=cv2.INTER_NEAREST,
            )
            match = cv2.matchTemplate(
                floorsImgs[7], scaled, cv2.TM_CCOEFF_NORMED,
            )
            _, score, _, position = cv2.minMaxLoc(match)
            result['quantizedRadarScaleMatches'].append({
                'scale': scale,
                'score': round(score, 4),
                'position': position,
            })
        result['scaledRouteMatches'] = []
        result['maskedRouteMatches'] = []
        for route_floor in sorted({item[2] for item in route_coordinates}):
            candidates = [item for item in route_coordinates if item[2] == route_floor]
            x0 = max(min(item[0] for item in candidates) - 31744 - 150, 0)
            y0 = max(min(item[1] for item in candidates) - 30976 - 150, 0)
            x1 = min(max(item[0] for item in candidates) - 31744 + 150, 2560)
            y1 = min(max(item[1] for item in candidates) - 30976 + 150, 2048)
            area = floorsImgs[route_floor][y0:y1, x0:x1]
            for scale in (0.2, 0.25, 0.3, 0.4, 0.5, 0.75, 1.0):
                scaled = cv2.resize(
                    quantized_radar, None, fx=scale, fy=scale,
                    interpolation=cv2.INTER_NEAREST,
                )
                match = cv2.matchTemplate(area, scaled, cv2.TM_CCOEFF_NORMED)
                _, score, _, position = cv2.minMaxLoc(match)
                result['scaledRouteMatches'].append({
                    'floor': route_floor,
                    'scale': scale,
                    'score': round(score, 4),
                    'coordinate': [
                        x0 + position[0] + scaled.shape[1] // 2 + 31744,
                        y0 + position[1] + scaled.shape[0] // 2 + 30976,
                        route_floor,
                    ],
                })
                mask = (scaled > 0).astype(np.uint8) * 255
                masked_match = cv2.matchTemplate(
                    area, scaled, cv2.TM_CCORR_NORMED, mask=mask,
                )
                _, masked_score, _, masked_position = cv2.minMaxLoc(masked_match)
                result['maskedRouteMatches'].append({
                    'floor': route_floor,
                    'scale': scale,
                    'score': round(masked_score, 4),
                    'coordinate': [
                        x0 + masked_position[0] + scaled.shape[1] // 2 + 31744,
                        y0 + masked_position[1] + scaled.shape[0] // 2 + 30976,
                        route_floor,
                    ],
                })
except Exception:
    result['error'] = traceback.format_exc()

(repo / '.runtime-diagnostic.json').write_text(
    json.dumps(result, ensure_ascii=False), encoding='utf-8'
)
