from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from PIL import Image
import cv2
from selenium.webdriver import ActionChains
import requests
from io import BytesIO
 
 
class MaoYanCode(object):
    # 初始化
    def __init__(self):
        self.url = 'https://open.yuewen.com/'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 10)
        
    def open(self):
        # 打开网页
        self.browser.get(self.url)
    
    # 定位背景图
    def bg_img_src(self):
        try:
            # 定位背景图片元素
            bg_element = self.wait.until(
                EC.presence_of_element_located((
                    By.CSS_SELECTOR,
                    'div.tc-bg-img#slideBg'
                ))
            )
            
            # 获取style属性
            style = bg_element.get_attribute('style')
            
            # 提取background-image URL
            import re
            url_match = re.search(r'background-image: url\("([^"]+)"\)', style)
            if url_match:
                return url_match.group(1)
            
            return None
                
        except Exception as e:
            print(f"获取背景图片URL失败: {str(e)}")
            return None
 
    # 定位缺块
    def jpp_img_src(self):
        try:
            # 等待元素加载
            target_element = self.wait.until(
                EC.presence_of_element_located((
                    By.CSS_SELECTOR,
                    'div.tc-fg-item[aria-label="拖动下方滑块完成拼图"]'
                ))
            )
            
            # 获取背景图片URL
            style = target_element.get_attribute('style')
            
            # 从style中提取background-image的url
            import re
            url_match = re.search(r'background-image: url\("([^"]+)"\)', style)
            if url_match:
                return url_match.group(1)
                
            return None
                
        except Exception as e:
            print(f"获取图片URL失败: {str(e)}")
            return None
    
    # 获取背景和缺块图片
    def get_img(self):
        bg_src = self.bg_img_src()
        jpp_src = self.jpp_img_src()
        response1 = requests.get(bg_src)
        image1 = Image.open(BytesIO(response1.content))
        image1.save('bg_img.png')
 
        response2 = requests.get(jpp_src)
        image2 = Image.open(BytesIO(response2.content))
        image2.save('jpp_img.png')
        return image1, image2
    
    # 定位滑块
    def slider_element(self):
        time.sleep(2)
        slider = self.wait.until(EC.presence_of_element_located(
            (By.XPATH,
             '//*[@class="tc-drag-thumb"]')))
        return slider
    # 登录按钮
    def login_btn_element(self):
        time.sleep(2)
        btn = self.wait.until(EC.presence_of_element_located(
            (By.XPATH,
             '//*[@class="op-header-info"]')))
        return btn
    def input_username(self, username="zywz2024013@163.com", retries=3):
        time.sleep(2)
        """
        输入用户名
        
        Args:
            username: 要输入的用户名
            retries: 重试次数
        """
        for attempt in range(retries):
            try:
                # 多种定位方式
                selectors = [
                    (By.CSS_SELECTOR, 'input[name="username"]'),
                    (By.CSS_SELECTOR, 'input[placeholder="用户名"]'),
                    (By.CSS_SELECTOR, '.el-input__inner[name="username"]'),
                    (By.XPATH, '//input[@name="username"]'),
                ]

                # 尝试每种定位方式
                for selector in selectors:
                    try:
                        # 等待元素可见和可交互
                        input_element = self.wait.until(
                            EC.element_to_be_clickable(selector)
                        )
                        
                        # 清除现有内容
                        input_element.clear()
                        
                        # 输入用户名
                        input_element.send_keys(username)
                        
                        # 验证输入是否成功
                        if self.verify_input(input_element, username):
                            self.logger.info("用户名输入成功")
                            return True
                            
                    except Exception as e:
                        self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}")
                        continue

            except Exception as e:
                self.logger.error(f"输入尝试 {attempt + 1}/{retries} 失败: {str(e)}")
                if attempt < retries - 1:
                    time.sleep(1)
                continue
                
        self.logger.error("所有输入尝试均失败")
        return False
    def input_password(self, password="Huang888888", retries=3):
        time.sleep(2)
        """
        输入密码
        
        Args:
            password: 要输入的密码
            retries: 重试次数
        """
        for attempt in range(retries):
            try:
                # 多种定位方式
                selectors = [
                    (By.CSS_SELECTOR, 'input[name="password"]'),
                    (By.CSS_SELECTOR, 'input[type="password"]'),
                    (By.CSS_SELECTOR, '.el-input__inner[name="password"]'),
                    (By.XPATH, '//input[@name="password"]'),
                ]

                for selector in selectors:
                    try:
                        # 等待密码输入框可交互
                        password_input = self.wait.until(
                            EC.element_to_be_clickable(selector)
                        )
                        
                        # 清除现有内容
                        password_input.clear()
                        
                        # 安全输入密码
                        self.safe_input_password(password_input, password)
                        
                        # 验证输入
                        if self.verify_password_input(password_input, password):
                            self.logger.info("密码输入成功")
                            return True
                            
                    except Exception as e:
                        self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}")
                        continue

            except Exception as e:
                self.logger.error(f"密码输入尝试 {attempt + 1}/{retries} 失败: {str(e)}")
                if attempt < retries - 1:
                    time.sleep(1)
                continue
                
        self.logger.error("所有密码输入尝试均失败")
        return False
    def click_login_button(self, retries=3):
        time.sleep(2)
        """
        点击登录按钮
        
        Args:
            retries: 重试次数
        Returns:
            bool: 是否点击成功
        """
        for attempt in range(retries):
            try:
                # 多种定位方式
                selectors = [
                    (By.CSS_SELECTOR, '.el-button.loginin'),
                    (By.CSS_SELECTOR, '[data-v-0101b427].el-button'),
                    (By.XPATH, "//button[contains(@class, 'loginin')]"),
                    (By.XPATH, "//button[.//span[text()='登录']]"),
                ]

                for selector in selectors:
                    try:
                        # 等待按钮可点击
                        button = self.wait.until(
                            EC.element_to_be_clickable(selector)
                        )
                        
                        # 确保按钮在视图中
                        self.scroll_to_element(button)
                        
                        # 尝试点击
                        if self.safe_click(button):
                            self.logger.info("登录按钮点击成功")
                            
                            # 等待页面响应
                            if self.wait_for_response():
                                return True
                            
                    except ElementClickInterceptedException:
                        # 如果按钮被遮挡，尝试JavaScript点击
                        self.logger.warning("按钮被遮挡，尝试JavaScript点击")
                        if self.js_click(button):
                            return True
                            
                    except Exception as e:
                        self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}")
                        continue

            except Exception as e:
                self.logger.error(f"点击尝试 {attempt + 1}/{retries} 失败: {str(e)}")
                if attempt < retries - 1:
                    time.sleep(1)
                continue
                
        self.logger.error("所有点击尝试均失败")
        return False
    def jump_login_view(self):
        time.sleep(2)
        login_btn = self.wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-v-0101b427]'))
        )
        login_btn.click()
    # 识别缺口
    def get_gap(self, gap_img):
        bg_img = cv2.imread('bg_img.png')
        tp_img = cv2.imread('jpp_img.png')
 
        # 识别图片边缘
        bg_edge = cv2.Canny(bg_img, 100, 200)
        tp_edge = cv2.Canny(tp_img, 100, 200)
 
        # 转换图片格式
        # 灰度图片转为RGB彩色图片
        bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
        tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
 
        # 缺口匹配
        res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 寻找最优匹配
 
        # 绘制方框
        # img.shape[:2] 获取图片的长、宽
        height, width = tp_pic.shape[:2]
        tl = max_loc  # 左上角点的坐标
        # cv2.rectangle(img, (x1, y1), (x2, y2), RGB颜色值, 边框宽度--->若为负则填充整个矩形)
        cv2.rectangle(bg_img, tl, (tl[0] + width - 15, tl[1] + height - 15),
                      (0, 0, 255), 2)  # 绘制矩形
        cv2.imwrite(gap_img, bg_img)  # 保存在本地
        # cv2.imshow('Show', bg_img)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()
        # 返回缺口的X坐标
        return tl[0]
    
    # 构造移动轨迹
    def get_track(self, distance):
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0
 
        while current < distance:
            if current < mid:
                # 加速度为正5
                a = 5
            else:
                # 加速度为负3
                a = -3
            # 初速度v0
            v0 = v
            # 当前速度v = v0 + at
            v = v0 + a * t
            # 移动距离x = v0t + 1/2 * a * t^2
            move = v0 * t + 1 / 2 * a * t * t
            # 当前位移
            current += move
            # 加入轨迹
            track.append(round(move))
        return track
    
    # 移动滑块
    def move_to_gap(self, slider, track):
        # click_and_hold()按住底部滑块
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x,
                                                      yoffset=0).perform()
        time.sleep(0.5)
        # release()松开鼠标
        ActionChains(self.browser).release().perform()
 
    def login(self):
        self.open()
        time.sleep(2)
        # 网速原因可能导致网页加载不完全，致使iframe报错
        iframe = self.wait.until(
            EC.presence_of_all_elements_located((By.TAG_NAME, 'iframe')))
        self.wait.until(
            EC.frame_to_be_available_and_switch_to_it(iframe[1]))
        

        login_btn = self.login_btn_element()
        login_btn.click()
   
        self.jump_login_view()

        self.input_username()

        self.input_password()

        self.click_login_button()

        self.get_img()
        slider = self.slider_element()
        slider.click()
        gap = self.get_gap('result.png')
        # 页面为360*360,图片为680*390,更改比例,减去初始位移
        gap_end = int((gap - 40) / 2)
        # 获取缺口
        print('缺口位置', gap_end)
        # 减去缺块白边
        gap_end -= 10
        # 获取移动轨迹
        track = self.get_track(gap_end)
        print('滑动轨迹', track)
        # 拖动滑块
        self.move_to_gap(slider, track)
 
 
if __name__ == '__main__':
    crack = MaoYanCode()
    crack.login()