import base64
import pyperclip
import win32clipboard
from io import BytesIO
from PIL import Image
import requests
import numpy as np
import cv2
import time
import os
from datetime import datetime
import random
import asyncio
import pyperclip  # 클립보드 데이터를 확인하기 위한 라이브러리

class ClipboardImageManager:
    def __init__(self, app, logger, browser_controller, debug=False):
        self.app = app
        self.logger = logger
        self.browser_controller = browser_controller  # BrowserController 인스턴스를 전달받음
        self.debug = debug  # 디버그 플래그를 클래스 변수로 사용

        self.debug = True

    async def get_clipboard_data(self):
        """클립보드의 텍스트 데이터를 가져옵니다."""
        try:
            return pyperclip.paste()  # 클립보드의 텍스트 데이터를 가져옴
        except Exception as e:
            self.logger.debug(f"클립보드 데이터를 가져오는 중 오류 발생: {e}", exc_info=True)
            return None

    def set_image_to_clipboard(self, image):
        """이미지를 클립보드에 넣는 함수 (Windows 전용)"""
        output = BytesIO()
        image.save(output, "BMP")
        self.logger.debug(f"이미지 데이터 BMP 변환")

        data = output.getvalue()[14:]  # BMP 헤더 제거
        output.close()
        self.logger.debug(f"이미지 BMP 헤더 제거")

        # 클립보드에 이미지 데이터 넣기
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
        win32clipboard.CloseClipboard()
        self.logger.debug(f"클립보드 데이터 저장 성공")
                  
    def save_image_to_path(self, image, path):
        try:
            if image:
                # 이미지를 저장 경로에 저장
                self.logger.info(f"이미지 저장 완료 : {path}")
                image.save(path)
                return path
            
        except Exception as e:
            raise RuntimeError(f"이미지 저장 중 오류 발생: {e}")

    # async def set_image_to_clipboard(self, image, crop_percentage=0.03, debug=False):
    #     """
    #     이미지를 클립보드에 넣는 함수 (Windows 전용, 크롭 후)
        
    #     :param image: PIL 이미지 객체
    #     :param crop_percentage: 크롭할 비율 (0.05는 5% 크롭을 의미)
    #     :param debug: True일 경우 크롭 전후 다양한 비율(3%, 5%, 7%)의 이미지를 디버그 용도로 저장
    #     """
    #     # 이미지의 크기 계산 (크롭 비율 적용)
    #     width, height = image.size
    #     left = width * crop_percentage
    #     top = height * crop_percentage
    #     right = width * (1 - crop_percentage)
    #     bottom = height * (1 - crop_percentage)

    #     # 크롭 적용
    #     cropped_image = image.crop((left, top, right, bottom))

    #     if debug:
    #         # 디버그 모드일 경우 크롭 전후 다양한 비율로 이미지 저장
    #         timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    #         original_image_path = os.path.join(os.getcwd(), f"original_image_{timestamp}.png")
    #         image.save(original_image_path)  # 크롭 전 이미지 저장
    #         self.logger.debug(f"크롭 전 이미지 저장됨: {original_image_path}")

    #         # 3%, 5%, 7% 크롭 이미지 저장
    #         crop_alternatives = [0.03, 0.05, 0.07]
    #         for crop in crop_alternatives:
    #             left_alt = width * crop
    #             top_alt = height * crop
    #             right_alt = width * (1 - crop)
    #             bottom_alt = height * (1 - crop)

    #             cropped_alt_image = image.crop((left_alt, top_alt, right_alt, bottom_alt))
    #             cropped_image_path = os.path.join(os.getcwd(), f"cropped_image_{int(crop*100)}_{timestamp}.png")
    #             cropped_alt_image.save(cropped_image_path)
    #             self.logger.debug(f"{int(crop*100)}% 크롭된 이미지 저장됨: {cropped_image_path}")

    #     # 크롭된 이미지를 BMP 형식으로 변환하여 클립보드에 넣기
    #     output = BytesIO()
    #     cropped_image.save(output, "BMP")
    #     data = output.getvalue()[14:]  # BMP 헤더 제거
    #     output.close()

    #     # 클립보드에 이미지 데이터 넣기
    #     win32clipboard.OpenClipboard()
    #     win32clipboard.EmptyClipboard()
    #     win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
    #     win32clipboard.CloseClipboard()

    #     self.logger.debug(f"{crop_percentage*100}% 크롭된 이미지가 클립보드에 저장되었습니다.")

    async def base64_to_image(self, base64_data):
        """Base64 데이터를 이미지로 변환하는 함수"""
        if base64_data.startswith('data:image'):
            header, encoded = base64_data.split(',', 1)
            img_data = base64.b64decode(encoded)
            image = Image.open(BytesIO(img_data))
            return image
        else:
            self.logger.debug("유효하지 않은 Base64 이미지 데이터입니다.")
            return None

    async def download_image_from_url(self, url, max_retries=3):
        """URL에서 이미지를 다운로드하고 PIL 이미지 객체로 반환"""
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
            "Accept-Language": "en-US,en;q=0.9",
            "Accept-Encoding": "gzip, deflate, br",
            "DNT": "1",  # Do Not Track 요청 헤더
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "Cache-Control": "max-age=0"
        }

        retries = 0
        while retries < max_retries:
            try:
                self.logger.debug(f"이미지 URL 다운로드 중: {url}")
                response = requests.get(url, headers=headers, stream=True)
                
                # 상태 코드가 200이 아니면 재시도
                if response.status_code == 200:
                    # OpenCV로 이미지를 로드하여 변환
                    image = np.asarray(bytearray(response.content), dtype="uint8")
                    image = cv2.imdecode(image, cv2.IMREAD_COLOR)
                    
                    # OpenCV에서 이미지를 PIL로 변환
                    if image is not None:
                        pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
                        return pil_image
                    else:
                        self.logger.debug(f"이미지 파일 형식이 올바르지 않습니다. 대상 URL: {url}")
                        return None
                else:
                    self.logger.debug(f"이미지 로딩 실패, HTTP 상태 코드: {response.status_code}. 재시도 {retries + 1}/{max_retries}")
                    retries += 1
                    await asyncio.sleep(random.randint(2, 5))  # 2~5초 대기 후 재시도
            except Exception as e:
                self.logger.debug(f"이미지 로딩 중 오류 발생: {e}. 재시도 {retries + 1}/{max_retries}")
                retries += 1
                await asyncio.sleep(random.randint(2, 5))  # 예외 발생 시 대기 후 재시도

        self.logger.debug("이미지 다운로드 최대 재시도 횟수를 초과했습니다.")
        return None

    async def process_clipboard(self, original_url, path=None):
        """클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기"""
        clipboard_data = await self.get_clipboard_data()

        # 1. 클립보드의 데이터가 Base64 이미지일 경우
        if clipboard_data.startswith('data:image'):
            self.logger.info("data:image 감지 : 이미지 데이터로 변환")
            image = await self.base64_to_image(clipboard_data)
            if image:
                width, _ = image.size
                self.logger.debug(f"Base64 이미지 크기: {width}px")
                
                # 가로 크기가 200픽셀 이상이면 크롭
                if width >= 200:
                    self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
                    cropped_image = self.crop_image(image)  # 크롭 메서드 사용
                    await self.set_image_to_clipboard(cropped_image)  # 클립보드에 저장
                    if path:
                        self.logger.debug("이미지 저장 시도...")
                        self.save_image_to_path(path)

                else:
                    self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
                    await self.clear_clipboard()
            else:
                self.logger.debug("Base64 이미지 변환 실패.")

        # 2. 클립보드에 이미지가 있을 경우
        elif self.is_clipboard_image():
            self.logger.info("클립보드 이미지 확인")
            image = self.get_image_from_clipboard()
            if image:
                width, _ = image.size
                self.logger.debug(f"클립보드에 있는 이미지 크기: {width}px")

                if width >= 200:
                    self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
                    cropped_image = self.crop_image(image)  # 크롭 메서드 사용
                    await self.set_image_to_clipboard(cropped_image)  # 클립보드에 저장
                    if path:
                        self.logger.debug("이미지 저장 시도...")
                        self.save_image_to_path(path)

                else:
                    self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
                    await self.clear_clipboard()

        # 3. html > whale-ocr 처리
        elif clipboard_data.strip() == "html > whale-ocr":
            self.logger.info("html > whale-ocr 감지 : 이미지 번역 실패 확인")
            if original_url:
                image = await self.download_image_from_url(original_url)
                if image:
                    self.logger.debug("원본 이미지 다운로드 성공, 클립보드에 저장 중...")
                    await self.set_image_to_clipboard(image)  # 크롭 없이 저장
                    if path:
                        self.logger.debug("이미지 저장 시도...")
                        self.save_image_to_path(path)
                else:
                    self.logger.debug("원본 이미지 다운로드 실패.")
            else:
                self.logger.debug("원본 이미지 URL을 찾을 수 없습니다.")

        else:
            self.logger.debug("클립보드에 처리할 수 있는 데이터가 없습니다.")

    def is_clipboard_image(self):
        """클립보드에 이미지가 있는지 확인하는 함수"""
        return win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB)

    def get_image_from_clipboard(self):
        """클립보드에서 이미지를 가져오는 함수"""
        try:
            win32clipboard.OpenClipboard()
            if self.is_clipboard_image():
                dib_data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
                image = Image.open(BytesIO(dib_data))
                return image
            else:
                self.logger.debug("클립보드에 이미지가 없습니다.")
        except Exception as e:
            self.logger.error(f"클립보드에서 이미지를 가져오는 중 오류 발생: {e}", exc_info=True)
        finally:
            win32clipboard.CloseClipboard()

        return None

    async def clear_clipboard(self):
        """클립보드를 비우는 함수"""
        try:
            win32clipboard.OpenClipboard()
            win32clipboard.EmptyClipboard()
            self.logger.debug("클립보드가 비워졌습니다.")
        except Exception as e:
            self.logger.error(f"클립보드를 비우는 중 오류 발생: {e}", exc_info=True)
        finally:
            win32clipboard.CloseClipboard()

    def crop_image(self, image, crop_percentage=0.01):
        """이미지를 주어진 퍼센트만큼 크롭하는 함수"""
        width, height = image.size
        left = width * crop_percentage
        top = height * crop_percentage
        right = width * (1 - crop_percentage)
        bottom = height * (1 - crop_percentage)

        cropped_image = image.crop((left, top, right, bottom))

        if self.debug:
            # 디버그 모드일 경우 크롭 전후 다양한 비율로 이미지 저장
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            original_image_path = os.path.join(os.getcwd(), f"original_image_{timestamp}.png")
            image.save(original_image_path)  # 크롭 전 이미지 저장
            self.logger.debug(f"크롭 전 이미지 저장됨: {original_image_path}")

            # 3%, 5%, 7% 크롭 이미지 저장
            crop_alternatives = [0.01, 0.02, 0.03]
            for crop in crop_alternatives:
                left_alt = width * crop
                top_alt = height * crop
                right_alt = width * (1 - crop)
                bottom_alt = height * (1 - crop)

                cropped_alt_image = image.crop((left_alt, top_alt, right_alt, bottom_alt))
                cropped_image_path = os.path.join(os.getcwd(), f"cropped_image_{int(crop*100)}_{timestamp}.png")
                cropped_alt_image.save(cropped_image_path)
                self.logger.debug(f"{int(crop*100)}% 크롭된 이미지 저장됨: {cropped_image_path}")

        return cropped_image