import os
import re
import yaml
import uuid
import logging
import socket
import platform
import threading
from urllib.parse import urlparse

import cv2

_logger = logging.getLogger(__name__)

def is_jetson():
    """Check if the code is running on an NVIDIA Jetson platform."""
    compat_file_path = "/proc/device-tree/compatible"
    if os.path.isfile(compat_file_path):
        with open(compat_file_path, "r") as file:
            compat_str = file.read()
            return "nvidia" in compat_str and "tegra" in compat_str
    return False

def is_pi():
    """Check if the code is running on a Raspberry Pi platform."""
    model_file_path = "/proc/device-tree/model"
    if os.path.isfile(model_file_path):
        with open(model_file_path, "r") as file:
            model_str = file.read().lower()
            return "raspberry pi" in model_str
    return False

def expand_camera_selector(camera_selector, id_to_item_id):
    """
    Expand camera selector to list of camera _ids.
    
    Args:
        camera_selector: str - can be "*", "0~30", or specific camera _id
        id_to_item_id: dict mapping _id to item_id
    
    Returns:
        list: List of camera _ids (integers)
    """
    camera_selector = camera_selector.strip()
    
    if camera_selector == '*':
        # All cameras
        return list(id_to_item_id.keys())
    elif '~' in camera_selector:
        # Range: "start~end"
        try:
            start, end = map(int, camera_selector.split('~'))
            if start > end:
                raise ValueError(f"Invalid camera range: {camera_selector}. Start must be <= end")
            # Get all camera _ids in range
            all_ids = set(id_to_item_id.keys())
            return sorted([cid for cid in all_ids if start <= cid <= end])
        except ValueError as e:
            raise ValueError(f"Invalid camera range format: {camera_selector}. Expected 'start~end' with integers. {e}")
    else:
        # Specific camera _id
        try:
            cam_id = int(camera_selector)
            if cam_id not in id_to_item_id:
                raise ValueError(f"Camera _id={cam_id} not found")
            return [cam_id]
        except ValueError as e:
            raise ValueError(f"Invalid camera selector: {camera_selector}. Expected '*', 'start~end', or camera _id. {e}")

def parse_frame_output_spec(spec_str):
    """
    Parse frame output spec using regex.
    Format: "camera_id:(height,width),color[,letterbox]"
    """
    pattern = r"^\s*(?P<selector>[^:]+):\s*\((?P<h>\d+),\s*(?P<w>\d+)\)\s*,\s*(?P<rest>.*)$"
    match = re.match(pattern, spec_str.strip())
    
    if not match:
        raise ValueError(f"Invalid frame output spec format: {spec_str}. "
                        f"Expected: 'camera_id:(height, width),color[,letterbox]'")

    camera_selector = match.group("selector").strip()
    height = int(match.group("h"))
    width = int(match.group("w"))
    
    options = [p.strip().lower() for p in match.group("rest").split(',')]
    
    color = None
    letterbox = False
    
    for opt in options:
        if opt in ['rgb', 'bgr']:
            color = opt.upper()
        elif opt == 'letterbox':
            letterbox = True
        elif opt == '':
            continue
        else:
            raise ValueError(f"Invalid frame output spec format: {spec_str}. Unknown option: {opt}")
    
    if color is None:
        raise ValueError(f"Invalid frame output spec format: {spec_str}. Color (rgb or bgr) must be specified")
    return camera_selector, width, height, color, letterbox

def parse_frame_outputs(frame_outputs_raw):
    """
    Parse frame_outputs configuration.
    """
    if frame_outputs_raw is None:
        return {}
    
    frame_outputs = {}
    for output_item in frame_outputs_raw:
        if not isinstance(output_item, dict):
            raise ValueError(f"Frame output item must be a dictionary, got {type(output_item)}")
        frame_outputs.update(output_item)
        
    return frame_outputs

def check_rtsp_port(rtsp_url, logger=None):
    """Check if RTSP port is available (TCP connection test)."""
    try:
        parsed = urlparse(rtsp_url)
        host = parsed.hostname
        port = parsed.port or 554
        
        if not host:
            return False
        
        is_localhost = host in ('127.0.0.1', 'localhost', '::1', '0.0.0.0')
        timeout = 0.5 if is_localhost else 2.0
        
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        try:
            result = sock.connect_ex((host, port))
            return result == 0
        finally:
            sock.close()
    except Exception as e:
        if logger:
            logger.warning(f"Failed to check RTSP port for {rtsp_url}: {e}")
        return False

def generate_rtsp_strings(cameras, backend):
    """
    Generate RTSP strings for each camera based on the backend.
    
    Args:
        cameras (dict): Dictionary of cameras
        backend (str): The backend to use ('gst', 'ffmpeg', or 'opencv')
    """
    cpu_arch = platform.machine()
    
    for item_id, cam in cameras.items():
        # Ensure rtsp_strings list exists
        if 'rtsp_strings' not in cam:
            cam['rtsp_strings'] = []
            
        for rtsp in cam.get('rtsp', []):
            ip = rtsp.get('ip')
            user = rtsp.get('user')
            pw = rtsp.get('pw')
            codec = rtsp.get('codec', 'h264')
            
            base_rtsp = f'rtsp://{user}:{pw}@{ip}' if user and pw else f'rtsp://{ip}'
            
            if backend == 'gst':
                from .backend import GStreamerCapture
                clean_rtsp = f'rtsp://{ip}'
                rtsp_str = GStreamerCapture.build_pipeline(clean_rtsp, cpu_arch, codec, user=user, pw=pw)
                if rtsp_str:
                    # Gstreamer pipeline put in front of the default rtsp
                    if len(cam['rtsp_strings']) > 1:
                        index = len(cam['rtsp_strings']) // 2
                        cam['rtsp_strings'].insert(index, rtsp_str)
                    else:
                        cam['rtsp_strings'].append(rtsp_str)
            
            # if Gstreamer is not used, use the default rtsp
            # if Gstreamer is used, this will be backup
            cam['rtsp_strings'].append(base_rtsp)

def extract_ip_from_rtsp(rtsp):
    """Extract IP address from RTSP string."""
    if not rtsp.startswith('rtsp://'):
        return None
    
    rtsp_without_protocol = rtsp[7:]
    if '@' in rtsp_without_protocol:
        rtsp_without_protocol = rtsp_without_protocol.split('@', 1)[1]
    
    ip_pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
    match = re.search(ip_pattern, rtsp_without_protocol)
    if match:
        return match.group(1)
    return None

def process_frame(frame, bc, max_branch_w=None, max_branch_h=None):
    """
    Standard OpenCV-based frame processing (resize/letterbox).
    bc: dict with WIDTH, HEIGHT, COLOR, LETTERBOX
    """
    
    tw, th, tc, lb = bc['WIDTH'], bc['HEIGHT'], bc['COLOR'], bc['LETTERBOX']
    if lb:
        # 使用傳入的最大分支尺寸作為參考，若無則使用原始影像尺寸
        if max_branch_w and max_branch_h:
            ref_w, ref_h = max_branch_w, max_branch_h
        else:
            ref_h, ref_w = frame.shape[:2]
            
        s = min(tw/ref_w, th/ref_h)
        # 改用 round() 以符合 ffmpeg.py 的邏輯
        nw, nh = int(round(ref_w*s)), int(round(ref_h*s))
        resized = cv2.resize(frame, (nw, nh))
        
        pw, ph = (tw-nw)//2, (th-nh)//2
        processed = cv2.copyMakeBorder(resized, ph, th-nh-ph, pw, tw-nw-pw, cv2.BORDER_CONSTANT, value=[0,0,0])
    else:
        processed = cv2.resize(frame, (tw, th))
    if tc == 'RGB': processed = cv2.cvtColor(processed, cv2.COLOR_BGR2RGB)
    return processed
