import threading
import re
import os

try:
    import cv2
    CV2_AVAILABLE = True
except ImportError:
    CV2_AVAILABLE = False

try:
    import numpy as np
    NUMPY_AVAILABLE = True
except ImportError:
    NUMPY_AVAILABLE = False

try:
    import easyocr
    EASYOCR_AVAILABLE = True
except ImportError:
    EASYOCR_AVAILABLE = False

try:
    from ultralytics import YOLO
    YOLO_AVAILABLE = True
except ImportError:
    YOLO_AVAILABLE = False

try:
    import torch
    USE_GPU = torch.cuda.is_available()
except ImportError:
    USE_GPU = False

from .config import LICENSE_PLATE_PATTERNS, LICENSE_PLATE_MODEL_PATH, YOLO_CONFIDENCE


class VideoAnalyzer:
    """영상 분석 클래스 - 얼굴, 텍스트, 번호판, 타투 감지"""

    _ocr_lock = threading.Lock()

    def __init__(self):
        self.ocr_reader = None
        self.face_cascade = None
        self.yolo_model = None

        if CV2_AVAILABLE:
            cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
            self.face_cascade = cv2.CascadeClassifier(cascade_path)

    def _init_ocr(self):
        """OCR 리더 초기화 (필요할 때만, 스레드 안전, GPU 가속 체크)"""
        if EASYOCR_AVAILABLE and self.ocr_reader is None:
            with self._ocr_lock:
                if self.ocr_reader is None:
                    gpu_status = "사용" if USE_GPU else "미사용"
                    print(f"  OCR 엔진 초기화 중... (GPU: {gpu_status})")
                    self.ocr_reader = easyocr.Reader(['ko', 'en'], gpu=USE_GPU, verbose=False)

    def _init_yolo(self):
        """YOLO 모델 초기화 (필요할 때만, 스레드 안전, GPU 가속 체크)"""
        if YOLO_AVAILABLE and self.yolo_model is None:
            with self._ocr_lock:
                if self.yolo_model is None:
                    device = "cuda" if USE_GPU else "cpu"
                    print(f"  YOLO 모델 로딩 중... (Device: {device})")
                    self.yolo_model = YOLO(str(LICENSE_PLATE_MODEL_PATH))
                    self.yolo_model.to(device)

    def extract_frames(self, video_path, num_frames=10):
        """영상에서 균등 간격으로 프레임 추출"""
        if not CV2_AVAILABLE:
            return []

        cap = cv2.VideoCapture(video_path)
        if not cap.isOpened():
            return []

        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        if total_frames <= 0:
            cap.release()
            return []

        frame_indices = [int(i * total_frames / (num_frames + 1)) for i in range(1, num_frames + 1)]
        frames = []

        for idx in frame_indices:
            cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
            ret, frame = cap.read()
            if ret:
                frames.append(frame)

        cap.release()
        return frames

    def detect_faces(self, frame):
        """Haar Cascade로 얼굴 감지"""
        if not CV2_AVAILABLE or self.face_cascade is None:
            return []

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        return self.face_cascade.detectMultiScale(
            gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)
        )

    def detect_text(self, frame, return_positions=False):
        """EasyOCR로 텍스트 감지 (스레드 안전)"""
        if not EASYOCR_AVAILABLE:
            return []

        self._init_ocr()
        try:
            h, w = frame.shape[:2]
            scale = 1.0

            # 가독성 개선을 위해 1080p 수준으로 리사이즈 (너무 작으면 인식률 저하)
            if w > 1280:
                scale = 1280 / w
                frame = cv2.resize(frame, (1280, int(h * scale)), interpolation=cv2.INTER_LANCZOS4)
            elif w < 640:
                # 너무 작은 경우 확대
                scale = 960 / w
                frame = cv2.resize(frame, (960, int(h * scale)), interpolation=cv2.INTER_CUBIC)

            resized_h, resized_w = frame.shape[:2]

            # 전처리: 그레이스케일 및 대비 강화 (옵션)
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
            processed = clahe.apply(gray)

            with self._ocr_lock:
                results = self.ocr_reader.readtext(processed)

            if return_positions:
                # 위치 정보와 함께 반환 (bbox, text, conf, y_ratio)
                texts_with_pos = []
                for r in results:
                    if r[2] > 0.25:
                        bbox = r[0]
                        # bbox의 y 중심점 계산 (0~1 비율)
                        y_center = (bbox[0][1] + bbox[2][1]) / 2
                        y_ratio = y_center / resized_h
                        texts_with_pos.append({
                            'text': r[1],
                            'conf': r[2],
                            'y_ratio': y_ratio
                        })
                return texts_with_pos

            # 신뢰도 임계값 0.25로 약간 하향 조정 (기존 0.3)
            return [r[1] for r in results if r[2] > 0.25]
        except Exception as e:
            print(f"  ⚠ OCR 에러: {e}")
            return []

    def detect_subtitle(self, frame):
        """자막 감지 - 화면 하단 60%~95% 영역의 텍스트만 필터링"""
        texts_with_pos = self.detect_text(frame, return_positions=True)
        if not texts_with_pos:
            return []

        # 자막은 주로 화면 하단에 위치 (y_ratio 0.6 ~ 0.95)
        subtitles = [t['text'] for t in texts_with_pos if 0.6 <= t['y_ratio'] <= 0.95]
        return subtitles

    def detect_license_plate(self, frame, texts=None):
        """
        ROI 기반 번호판 감지 (최적화 버전)
        1. YOLO로 번호판 영역(ROI)을 먼저 찾음
        2. 찾은 영역만 잘라서 OCR 수행 (속도 및 정확도 향상)
        """
        if not YOLO_AVAILABLE or frame is None:
            return False

        try:
            self._init_yolo()
            results = self.yolo_model(frame, verbose=False, conf=YOLO_CONFIDENCE)
            
            yolo_detected = False
            roi_ocr_matched = False

            for r in results:
                # 0: license plate
                for box in r.boxes:
                    if box.cls == 0:
                        yolo_detected = True

                        # ROI 크기 필터링 (너무 작거나 큰 영역 제외)
                        x1, y1, x2, y2 = map(int, box.xyxy[0])
                        roi_w, roi_h = x2 - x1, y2 - y1
                        frame_h, frame_w = frame.shape[:2]
                        roi_area_ratio = (roi_w * roi_h) / (frame_w * frame_h)

                        # ROI가 전체 화면의 0.1% ~ 20% 사이여야 함
                        if roi_area_ratio < 0.001 or roi_area_ratio > 0.2:
                            continue

                        # ROI 크로핑 및 타겟 OCR
                        h, w = frame.shape[:2]
                        # 패딩 10% 추가
                        pad_w = int((x2 - x1) * 0.1)
                        pad_h = int((y2 - y1) * 0.1)
                        
                        crop_x1 = max(0, x1 - pad_w)
                        crop_y1 = max(0, y1 - pad_h)
                        crop_x2 = min(w, x2 + pad_w)
                        crop_y2 = min(h, y2 + pad_h)
                        
                        roi = frame[crop_y1:crop_y2, crop_x1:crop_x2]
                        if roi.size > 0:
                            # ROI에 대해서만 OCR 실행
                            roi_texts = self.detect_text(roi)
                            if roi_texts:
                                combined_roi = "".join([re.sub(r'[^0-9가-힣]', '', t) for t in roi_texts])
                                for pattern in LICENSE_PLATE_PATTERNS:
                                    # 개별 텍스트 및 결합 텍스트 확인
                                    if any(re.search(pattern, re.sub(r'[^0-9가-힣]', '', t)) for t in roi_texts) or \
                                       re.search(pattern, combined_roi):
                                        roi_ocr_matched = True
                                        break
                        
                        if roi_ocr_matched: break
                if roi_ocr_matched: break

            # 최종 판정: YOLO 감지 + OCR 패턴 매칭 둘 다 만족해야 함 (오탐지 방지)
            if yolo_detected and roi_ocr_matched:
                return True
                
        except Exception as e:
            # print(f"  ⚠ 번호판 ROI 분석 에러: {e}")
            pass

        return False

    def detect_tattoo(self, frame):
        """피부 영역에서 타투(어두운 잉크 패턴) 감지"""
        if not CV2_AVAILABLE or not NUMPY_AVAILABLE:
            return False

        try:
            hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

            # 피부색 범위
            lower_skin = np.array([0, 30, 80], dtype=np.uint8)
            upper_skin = np.array([17, 170, 255], dtype=np.uint8)
            skin_mask = cv2.inRange(hsv, lower_skin, upper_skin)

            # 노이즈 제거
            kernel = np.ones((5, 5), np.uint8)
            skin_mask = cv2.morphologyEx(skin_mask, cv2.MORPH_OPEN, kernel)
            skin_mask = cv2.morphologyEx(skin_mask, cv2.MORPH_CLOSE, kernel)

            skin_pixels = cv2.countNonZero(skin_mask)
            total_pixels = frame.shape[0] * frame.shape[1]

            # 피부 영역 최소 10% 필요
            if skin_pixels < total_pixels * 0.10:
                return False

            # 피부 영역 내 어두운 픽셀(타투) 감지
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            skin_gray = cv2.bitwise_and(gray, gray, mask=skin_mask)
            dark_mask = cv2.inRange(skin_gray, 1, 80)

            dark_pixels = cv2.countNonZero(dark_mask)
            dark_ratio = dark_pixels / max(skin_pixels, 1)

            # 어두운 영역이 3~35%일 때 타투로 판정
            if 0.03 < dark_ratio < 0.35:
                contours, _ = cv2.findContours(dark_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
                significant = [c for c in contours if cv2.contourArea(c) > 100]
                return len(significant) >= 1

            return False
        except:
            return False

    def analyze(self, video_path, target_category=None):
        """영상 전체 분석"""
        results = {
            'face': False,
            'text': False,
            'license_plate': False,
            'tattoo': False,
            'face_count': 0,
            'detected_texts': [],
            'first_detection_sec': None,
            'first_detection_ts': None
        }

        if not CV2_AVAILABLE:
            print("  ⚠ OpenCV 미설치")
            return results

        # 영상 정보 가져오기
        cap = cv2.VideoCapture(video_path)
        if not cap.isOpened():
            return results
        
        fps = cap.get(cv2.CAP_PROP_FPS) or 30
        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        cap.release()

        # 분석 프레임 수 증가 (10 -> 20)
        num_analysis_frames = 20
        # 영상이 아주 긴 경우(10분 이상) 더 많은 프레임 추출
        if total_frames / fps > 600:
            num_analysis_frames = 30
            
        frame_indices = [int(i * total_frames / (num_analysis_frames + 1)) for i in range(1, num_analysis_frames + 1)]
        
        all_texts = []
        total_faces = 0

        cap = cv2.VideoCapture(video_path)
        for idx in frame_indices:
            cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
            ret, frame = cap.read()
            if not ret: continue

            # 현재 프레임의 시간(초)
            current_sec = idx / fps
            detected_now = False

            # 얼굴
            faces = self.detect_faces(frame)
            if len(faces) > 0:
                results['face'] = True
                total_faces += len(faces)
                detected_now = True

            # 번호판 감지 (타겟 카테고리가 번호판인 경우 우선 수행 - ROI 최적화)
            if target_category == 'license_plate':
                if self.detect_license_plate(frame):
                    results['license_plate'] = True
                    detected_now = True

            # 텍스트/자막 감지
            if target_category == 'text':
                # 자막 영역 필터링 (화면 하단 60%~95%)
                texts = self.detect_subtitle(frame)
                if texts:
                    results['text'] = True
                    all_texts.extend(texts)
                    detected_now = True
            elif detected_now and target_category != 'license_plate':
                texts = self.detect_text(frame)
                if texts:
                    results['text'] = True
                    all_texts.extend(texts)
            elif target_category == 'license_plate' and not results['license_plate']:
                # 번호판을 못 찾은 경우에만 전체 화면 OCR 한 번 더 시도 (보수적 접근)
                texts = self.detect_text(frame)
                if texts:
                    all_texts.extend(texts)
                    # 이미 detect_license_plate에서 결과가 안 나왔으므로 여기서는 텍스트만 수집

            # 타투
            if self.detect_tattoo(frame):
                results['tattoo'] = True
                detected_now = True

            # 첫 감지 시점 기록
            if detected_now and results['first_detection_sec'] is None:
                results['first_detection_sec'] = current_sec
                m, s = int(current_sec // 60), int(current_sec % 60)
                results['first_detection_ts'] = f"{m:02d}:{s:02d}"

        cap.release()

        if all_texts:
            results['detected_texts'] = list(set(all_texts))[:10]

        results['face_count'] = total_faces
        return results


def check_dependencies():
    """의존성 체크"""
    missing = []
    if not CV2_AVAILABLE:
        missing.append("opencv-python")
    if not EASYOCR_AVAILABLE:
        missing.append("easyocr")
    if not NUMPY_AVAILABLE:
        missing.append("numpy")
    return missing
