"""
차선 인식 모듈

HSV 색상 기반으로 흰색/노란색 차선을 검출합니다.
"""

import cv2
import numpy as np


class LaneDetector:
    """HSV 색상 기반 차선 검출기"""

    def __init__(self, config=None):
        """
        Args:
            config (dict): 차선 검출 설정
                - roi_y_start: ROI 시작 y 비율 (0.0-1.0)
                - white_hsv_lower: 흰색 하한 [H, S, V]
                - white_hsv_upper: 흰색 상한 [H, S, V]
                - yellow_hsv_lower: 노란색 하한 [H, S, V]
                - yellow_hsv_upper: 노란색 상한 [H, S, V]
        """
        # 기본 설정
        default_config = {
            'roi_y_start': 0.5,
            'roi_near_y': 0.9,
            'roi_far_y': 0.3,
            'roi_x_ratio': 0.8,
            'white_hsv_lower': [0, 0, 220],
            'white_hsv_upper': [180, 30, 255],
            'yellow_hsv_lower': [20, 100, 100],
            'yellow_hsv_upper': [30, 255, 255]
        }

        self.config = config if config else default_config

        # HSV 범위를 numpy 배열로 변환
        self.white_lower = np.array(self.config['white_hsv_lower'], dtype=np.uint8)
        self.white_upper = np.array(self.config['white_hsv_upper'], dtype=np.uint8)
        self.yellow_lower = np.array(self.config['yellow_hsv_lower'], dtype=np.uint8)
        self.yellow_upper = np.array(self.config['yellow_hsv_upper'], dtype=np.uint8)

        self.roi_y_start = self.config['roi_y_start']
        self.roi_near_y = self.config.get('roi_near_y', 0.9)
        self.roi_far_y = self.config.get('roi_far_y', 0.3)
        # 가로 ROI 폭 비율 (중앙 기준, 1.0=전체 폭). 클수록 좌우 넓게 봄
        self.roi_x_ratio = self.config.get('roi_x_ratio', 0.8)

        # 차선 색 모드: 'white' | 'black' | 'orange' (1/2/3 키로 선택)
        self.lane_color = self.config.get('lane_color', 'white')
        # 색상별 HSV 범위 (H:0-179, S/V:0-255)
        #  - 검정: 밝기(V)가 낮은 픽셀 (색상·채도 무관)
        #  - 주황: 빨강~노랑 사이 색상 + 높은 채도
        self.color_ranges = {
            'black':  (np.array(self.config.get('black_hsv_lower', [0, 0, 0]), dtype=np.uint8),
                       np.array(self.config.get('black_hsv_upper', [180, 255, 70]), dtype=np.uint8)),
            'orange': (np.array(self.config.get('orange_hsv_lower', [5, 100, 100]), dtype=np.uint8),
                       np.array(self.config.get('orange_hsv_upper', [22, 255, 255]), dtype=np.uint8)),
        }

    def set_lane_color(self, color):
        """차선 색 모드 변경: 'white' | 'black' | 'orange'"""
        if color in ('white', 'black', 'orange'):
            self.lane_color = color
            return True
        return False

    def detect(self, frame):
        """
        차선 검출 및 중심 계산 (하이브리드: 위치 + 각도)

        Args:
            frame (numpy.ndarray): 입력 프레임 (BGR)

        Returns:
            tuple: (lane_center_near, lane_angle, debug_frame)
                - lane_center_near: 가까운 영역의 차선 중심 x 좌표 (None: 미검출)
                - lane_angle: 차선 각도 (degrees, None: 미검출)
                - debug_frame: 디버그 시각화 프레임
        """
        if frame is None:
            return None, None, None

        height, width = frame.shape[:2]

        # 1. 2개 ROI 설정 (가까운 영역 + 먼 영역) - 화면 중앙 50%만 사용
        roi_near_y = int(height * self.roi_near_y)  # 하단 (가까운 곳)
        roi_far_y = int(height * self.roi_far_y)    # 상단 (먼 곳)

        # 화면 중앙 가로 ROI 영역 계산 (roi_x_ratio 비율, 중앙 기준)
        center_x = width // 2
        half_width = int(width * self.roi_x_ratio / 2)
        x_start = center_x - half_width
        x_end = center_x + half_width

        roi_near = frame[roi_near_y:, x_start:x_end]
        roi_far = frame[roi_far_y:roi_near_y, x_start:x_end]

        # 2. 각 영역에서 차선 중심 검출 (윤곽선도 반환)
        lane_center_near, contour_near = self._detect_lane_center(roi_near, return_contour=True)
        lane_center_far, contour_far = self._detect_lane_center(roi_far, return_contour=True)

        # 3. 검출된 중심을 전체 프레임 좌표로 변환
        if lane_center_near is not None:
            lane_center_near += x_start
        if lane_center_far is not None:
            lane_center_far += x_start

        # 3. 각도 계산
        lane_angle = None
        if lane_center_near is not None and lane_center_far is not None:
            # 두 점의 차이
            dx = lane_center_far - lane_center_near
            dy = roi_near_y - roi_far_y  # y 거리 (픽셀)

            # 각도 계산 (arctan2 사용)
            lane_angle = np.degrees(np.arctan2(dx, dy))

        # 4. 디버그 시각화
        debug_frame = self._create_debug_frame_hybrid(
            frame, lane_center_near, lane_center_far, lane_angle,
            roi_near_y, roi_far_y, contour_near, contour_far, x_start, x_end
        )

        return lane_center_near, lane_angle, debug_frame

    def _detect_lane_center(self, roi, return_mask=False, return_contour=False):
        """
        단일 ROI 영역에서 차선 중심 검출

        Args:
            roi (numpy.ndarray): ROI 영역
            return_mask (bool): 마스크도 반환할지 여부
            return_contour (bool): 윤곽선도 반환할지 여부

        Returns:
            int or tuple: 차선 중심 x 좌표 (None: 미검출)
                - return_mask=True: (center, mask) 튜플
                - return_contour=True: (center, contour) 튜플
        """
        if roi is None or roi.size == 0:
            if return_mask:
                return (None, None)
            elif return_contour:
                return (None, None)
            else:
                return None

        # HSV 변환
        hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

        # 색상 필터링 (선택된 차선 색: white/black/orange)
        # white는 런타임 튜닝(,/. ;/' 키)으로 바뀌는 white_lower/upper를 사용
        if self.lane_color == 'white':
            lower, upper = self.white_lower, self.white_upper
        else:
            lower, upper = self.color_ranges[self.lane_color]
        mask = cv2.inRange(hsv, lower, upper)

        # 노이즈 제거
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
        mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
        mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

        # 윤곽선 검출
        contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        if len(contours) == 0:
            if return_mask:
                return (None, mask)
            elif return_contour:
                return (None, None)
            else:
                return None

        # ROI 중앙 계산
        roi_height, roi_width = roi.shape[:2]
        roi_center_x = roi_width // 2

        # 면적 100 이상인 윤곽선 필터링
        valid_contours = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if area < 100:
                continue

            # 중심 계산
            M = cv2.moments(contour)
            if M["m00"] > 0:
                cx = int(M["m10"] / M["m00"])
                # 중앙까지의 거리 계산
                distance_to_center = abs(cx - roi_center_x)
                # (윤곽선, 중앙거리, 중심x) 저장
                valid_contours.append((contour, distance_to_center, cx))

        if len(valid_contours) == 0:
            if return_mask:
                return (None, mask)
            elif return_contour:
                return (None, None)
            else:
                return None

        # 중앙에 가장 가까운 것 선택
        selected_contour, distance, center = min(valid_contours, key=lambda x: x[1])

        if return_mask:
            return (center, mask)
        elif return_contour:
            return (center, selected_contour)
        else:
            return center

    def _create_debug_frame(self, frame, roi, mask, lane_center_x, roi_y):
        """디버그 프레임 생성"""
        height, width = frame.shape[:2]
        debug_frame = frame.copy()

        # ROI 영역 표시
        cv2.rectangle(debug_frame, (0, roi_y), (width, height), (255, 0, 0), 2)

        # 마스크를 컬러로 변환하여 ROI 위치에 오버레이
        mask_colored = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
        mask_colored = cv2.applyColorMap(mask_colored, cv2.COLORMAP_HOT)
        debug_frame[roi_y:, :] = cv2.addWeighted(debug_frame[roi_y:, :], 0.7, mask_colored, 0.3, 0)

        # 화면 중심선
        frame_center_x = width // 2
        cv2.line(debug_frame, (frame_center_x, 0), (frame_center_x, height), (0, 255, 255), 2)

        # 차선 중심선 및 오차
        if lane_center_x is not None:
            # 차선 중심선
            cv2.line(debug_frame, (lane_center_x, roi_y), (lane_center_x, height), (0, 255, 0), 3)

            # 오차 화살표
            cv2.arrowedLine(debug_frame,
                            (frame_center_x, height - 30),
                            (lane_center_x, height - 30),
                            (0, 0, 255), 3, tipLength=0.3)

            # 수치 표시
            error = lane_center_x - frame_center_x
            cv2.putText(debug_frame, f"Error: {error}px", (10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            cv2.putText(debug_frame, f"Lane Center: {lane_center_x}", (10, 60),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
        else:
            cv2.putText(debug_frame, "LANE NOT DETECTED", (10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

        return debug_frame

    def _create_debug_frame_hybrid(self, frame, lane_center_near, lane_center_far, lane_angle, roi_near_y, roi_far_y, contour_near=None, contour_far=None, x_start=0, x_end=None):
        """하이브리드 방식용 디버그 프레임 생성"""
        height, width = frame.shape[:2]
        debug_frame = frame.copy()

        if x_end is None:
            x_end = width

        # 감지된 차선 윤곽선만 초록색으로 채우기
        if contour_near is not None:
            # 윤곽선을 전체 프레임 좌표로 변환
            contour_shifted = contour_near.copy()
            contour_shifted[:, 0, 0] += x_start  # x 좌표 이동
            contour_shifted[:, 0, 1] += roi_near_y  # y 좌표 이동

            # 윤곽선을 채워진 형태로 그리기 (투명한 초록색)
            overlay = debug_frame.copy()
            cv2.drawContours(overlay, [contour_shifted], -1, (0, 255, 0), -1)
            debug_frame = cv2.addWeighted(debug_frame, 0.4, overlay, 0.6, 0)

        if contour_far is not None:
            # 윤곽선을 전체 프레임 좌표로 변환
            contour_shifted = contour_far.copy()
            contour_shifted[:, 0, 0] += x_start  # x 좌표 이동
            contour_shifted[:, 0, 1] += roi_far_y  # y 좌표 이동

            # 윤곽선을 채워진 형태로 그리기 (투명한 초록색)
            overlay = debug_frame.copy()
            cv2.drawContours(overlay, [contour_shifted], -1, (0, 255, 0), -1)
            debug_frame = cv2.addWeighted(debug_frame, 0.4, overlay, 0.6, 0)

        # ROI 영역 표시
        cv2.line(debug_frame, (0, roi_near_y), (width, roi_near_y), (255, 0, 0), 2)  # 하단 ROI
        cv2.line(debug_frame, (0, roi_far_y), (width, roi_far_y), (0, 255, 0), 2)    # 상단 ROI

        # 중앙 50% 검출 영역 표시 (세로 선)
        cv2.line(debug_frame, (x_start, 0), (x_start, height), (255, 255, 0), 1)  # 왼쪽 경계
        cv2.line(debug_frame, (x_end, 0), (x_end, height), (255, 255, 0), 1)      # 오른쪽 경계

        cv2.putText(debug_frame, "Near ROI", (10, roi_near_y - 10),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
        cv2.putText(debug_frame, "Far ROI", (10, roi_far_y - 10),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
        cv2.putText(debug_frame, "Detection Zone (50%)", (x_start + 5, 20),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)

        # 화면 중심선
        frame_center_x = width // 2
        cv2.line(debug_frame, (frame_center_x, 0), (frame_center_x, height), (0, 255, 255), 2)

        # 차선 중심점 표시
        if lane_center_near is not None:
            cv2.circle(debug_frame, (lane_center_near, roi_near_y + 20), 8, (255, 0, 255), -1)
            cv2.putText(debug_frame, "Near", (lane_center_near + 15, roi_near_y + 25),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)

        if lane_center_far is not None:
            cv2.circle(debug_frame, (lane_center_far, roi_far_y + 20), 8, (255, 255, 0), -1)
            cv2.putText(debug_frame, "Far", (lane_center_far + 15, roi_far_y + 25),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)

        # 두 점을 잇는 선
        if lane_center_near is not None and lane_center_far is not None:
            cv2.line(debug_frame,
                    (lane_center_near, roi_near_y + 20),
                    (lane_center_far, roi_far_y + 20),
                    (0, 255, 0), 3)

            # 각도 표시
            if lane_angle is not None:
                angle_text = f"Angle: {lane_angle:.1f}deg"
                cv2.putText(debug_frame, angle_text, (10, 30),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

            # 위치 오차 표시
            position_error = lane_center_near - frame_center_x
            cv2.putText(debug_frame, f"Position Error: {position_error}px", (10, 60),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
        else:
            cv2.putText(debug_frame, "LANE NOT DETECTED", (10, 30),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

        return debug_frame


# ===== 테스트 코드 =====
if __name__ == "__main__":
    import json
    import os

    # 설정 파일 로드
    config_path = os.path.join(os.path.dirname(__file__), "..", "config", "config.json")
    if os.path.exists(config_path):
        with open(config_path, 'r') as f:
            config = json.load(f)
            lane_config = config.get('lane_detection', {})
    else:
        lane_config = {}

    # 차선 검출기 생성
    detector = LaneDetector(lane_config)

    # ESP32-CAM IP 주소
    esp32_ip = config.get('esp32', {}).get('ip', '192.168.137.130')
    stream_url = f"http://{esp32_ip}/stream"

    print(f"=== ESP32-CAM 차선 인식 테스트 ===")
    print(f"연결 중: {stream_url}")

    # ESP32-CAM 스트림 연결
    cap = cv2.VideoCapture(stream_url)

    # 버퍼 크기 최소화 (딜레이 감소)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

    if not cap.isOpened():
        print(f"연결 실패: {stream_url}")
        print("브라우저에서 스트림이 열려있으면 닫아주세요.")
        exit(1)

    print("연결 성공!")
    print("조작 키: 'q' 종료, 'd' 디버그 뷰 토글\n")

    show_debug = True
    frame_count = 0
    detection_count = 0

    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                print("프레임 읽기 실패")
                break

            # 프레임 회전 없음 (0도)
            # frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)

            # 좌우 반전 (미러링)
            frame = cv2.flip(frame, 1)

            frame_count += 1

            # 차선 검출
            lane_center, lane_angle, debug_frame = detector.detect(frame)

            if lane_center is not None:
                detection_count += 1

            # 화면 표시
            display_frame = debug_frame if (show_debug and debug_frame is not None) else frame

            if display_frame is not None:
                # 검출률 표시
                detection_rate = (detection_count / frame_count * 100) if frame_count > 0 else 0
                h, w = display_frame.shape[:2]
                cv2.putText(display_frame, f"Detection: {detection_rate:.1f}%", (10, h - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

                cv2.imshow('ESP32-CAM Lane Detection', display_frame)

            # 키 입력
            key = cv2.waitKey(1) & 0xFF
            if key == ord('q'):
                break
            elif key == ord('d'):
                show_debug = not show_debug
                print(f"디버그 뷰: {'ON' if show_debug else 'OFF'}")

    except KeyboardInterrupt:
        print("\n사용자 중단")

    finally:
        cap.release()
        cv2.destroyAllWindows()

        # 통계 출력
        print("\n=== 테스트 결과 ===")
        print(f"총 프레임: {frame_count}")
        print(f"차선 검출: {detection_count}")
        if frame_count > 0:
            detection_rate = (detection_count / frame_count * 100)
            print(f"검출률: {detection_rate:.1f}%")
        print("종료")
