import os
from dotenv import load_dotenv
from time import sleep
from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException, NoSuchElementException, StaleElementReferenceException, TimeoutException

load_dotenv()

# Constante global do módulo
TIMEOUT = 300

class Driver_Selenium():
    """Destinada a criação e gerenciamento de drivers do selenium."""

    def __init__(self, ipselenoid=None, browser='chrome', local=False, timeout='5m', headless=True, options=None):
        self.browser = browser
        self.local = local
        self.timeout = timeout
        self.headless = headless
        self.ipselenoid = ipselenoid or os.getenv('IPSELENOID')
        self.options = options

    def criar_driver(self):
        """
        Cria um driver para Chrome ou Firefox, local ou remoto.

        Args:
            browser (str): O navegador a ser usado ('chrome' ou 'firefox'). Padrão: 'chrome'.
            local (bool): False (padrão) para Selenoid, True para local.
            timeout (str): Timeout de inatividade da sessão no Selenoid.
            headless (bool): True (padrão) para executar em modo headless.

        Returns:
            WebDriver: A instância do driver do Selenium.
        """
        browser = self.browser.lower()
        print(f"Criando driver para '{browser}' em modo {'local' if self.local else 'remoto'}...")
        options = self.options

        if browser == 'chrome':
            browser_name = "chrome"
            if not options:
                options = ChromeOptions()
                options.add_argument("--disable-notifications") #Desabilita notificações como "Aceitar cookies"
                options.add_argument("--no-default-browser-check") #Desabilita a verificação se o chrome é o navegador padrão
                options.add_argument("--disable-infobars") #desabilita a mensagem "Esse chrome está sendo controlado por um testo automatizado"
                options.add_argument("--disable-save-password-bubble") #não salva as senhas
                options.add_argument("--disable-popup-blocking") #desabilita pop-ups (nao influencia em alerts())
                options.add_argument("--disable-extensions") #desabilita extensões
                options.add_argument("--incognito")  # modo anônimo
                options.add_experimental_option("prefs", {
                    "credentials_enable_service": False,
                    "profile.password_manager_enabled": False
                })
            if self.headless:
                options.add_argument("--headless=new")
                options.add_argument("--blink-settings=imagesEnabled=false") # Específico do Chrome
        
        elif browser == 'firefox':
            browser_name = "firefox"
            if not options:
                options = FirefoxOptions()
            if self.headless:
                options.add_argument("-headless")
        
        else:
            raise ValueError(f"Navegador '{browser}' não suportado. Use 'chrome' ou 'firefox'.")

        if self.local:
            if browser == 'chrome':
                driver = webdriver.Chrome(options=options)
            elif browser == 'firefox':
                options.binary_location = r'/usr/bin/firefox'
                driver = webdriver.Firefox(options=options)
        else: # Remoto (Selenoid)
            options.browser_version = "latest"
            options.set_capability("browserName", browser_name)
            options.set_capability(
                "selenoid:options", {
                    "enableVNC": True,
                    "sessionTimeout": self.timeout
                }
            )
            driver = webdriver.Remote(
                command_executor=self.ipselenoid,
                options=options
            )
        
        if self.headless:
            driver.set_window_size(1920, 1080)
        else:
            driver.maximize_window()
        
            
        return driver

    def validar_driver(driver):
        """Verifica se o driver está ativo. Se não, cria um novo e o retorna."""
        if driver:
            try:
                _ = driver.title
                print("Sessão do driver está ativa.")
                return driver 
            except Exception:
                print("Sessão do driver inativa. Recriando...")
                try:
                    driver.quit()
                except Exception:
                    pass

        novo_driver = Driver_Selenium.criar_driver()
        return novo_driver


class Zendesk_Selenium:
    """Destinada à facilitar a automação UI da zendesk junto ao selenium."""
    def __init__(self, driver, usuario, senha, instancia):
        """Chamada inicial para configurar o ambiente.

        Args:
            driver: Driver criado na classe Driver Selenium.
            usuario: Usuario para realizar o login na zendesk.
            senha: Senha para realizar o login na zendesk.
            instancia: Instancia da zendesk para que o login possa ser efetuado corretamente.
        """
        self.driver = driver
        self.usuario = usuario
        self.senha = senha
        self.instancia = instancia


    def login(self):
        """Realiza o login na plataforma zendesk."""
        link = self.instancia
        link = f'https://{link}.zendesk.com/access/normal'
        self.driver.get(link)
        try:
            login_zendesk = WebDriverWait(self.driver, TIMEOUT).until(
                EC.element_to_be_clickable((By.ID, 'user_email'))
            )
            login_zendesk.send_keys(self.usuario)

            pass_zendesk = self.driver.find_element(By.ID, 'user_password')
            pass_zendesk.send_keys(self.senha)

            entrar_zendesk = self.driver.find_element(By.ID, 'sign-in-submit-button')
            entrar_zendesk.click()
        except:
            pass

        WebDriverWait(self.driver, TIMEOUT).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-test-id="home_icon"]'))
        )


    def play(self, fila:int):
        """Inicia o play automatico de uma visualização da zendesk.

        Args:
            fila (int): Número da visualização da zendesk.
        """
        self.driver.get(self.fila)
        play = WebDriverWait(self.driver, TIMEOUT).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-test-id="views_views-header-play-button"]'))
                )
        play.click()
        sleep(5)

    def fechar_dois_pacotes(self):
        """Procura e fecha o MODAL de 2 pacotes dentro do ticket na zendesk.
        
        Returns:
            True quando modal foi encontrado e fechado, False quando o modal não foi encontrado no ticket.
        """
        try:
            dois_pacotes = WebDriverWait(self.driver, 30).until(
                            EC.element_to_be_clickable((By.CSS_SELECTOR, 'header.modal-header a.close'))
                        )
            dois_pacotes.click()
            return True
        except:
            return False
        
    def selecionar_dropdown(self, id: int, valor_campo: str):
        """ Seleciona a opção desejada dentro de um campo dropdown na zendesk.

        Args:
            id (int): Identificador único do custom field na zendesk.
            valor_campo (str): Exato valor a ser preenchido nas opções do dropdown.
        """
        seletor = f'[data-test-id="ticket-form-field-dropdown-field-{id}"] [data-garden-id="typography.ellipsis"]'
        campo = WebDriverWait(self.driver, 10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, seletor))
        )
        campo.click()

        menu = WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, '[data-test-id="ticket-form-field-dropdown-menu"]'))
        )

        opcao = WebDriverWait(menu, 10).until(
            EC.element_to_be_clickable((By.XPATH, f'.//li[.//span[normalize-space()="{valor_campo}"]]'))
        )
        opcao.click()
    
    def obter_valores_input(self, ids:dict):
        """Verifica e retorna os valores preenchidos dentro campos input.

        Args:
            ids (dict): Dicionario com Nome do campo e ID unico do campo input.

        Returns:
            Dicionario com Nome dos campos e valor dentro dele.
        """
        valores_campos = {}
        for nome_campo, id_campo in ids.items():
            try:
                seletor = f'.custom_field_{id_campo} input[data-test-id="ticket-fields-text-field"]'
                elemento = self.driver.find_element(By.CSS_SELECTOR, seletor)
                valor_elemento = elemento.get_attribute("value")
                valores_campos[nome_campo] = valor_elemento

            except NoSuchElementException:
                print(f"Aviso: Campo com seletor '{seletor}' não foi encontrado.")
                valores_campos[nome_campo] = None
            
        return valores_campos
        
    def obter_valores_dropdown(self, ids:dict):
        """Realiza a captura de todos os valores dentro de campos dropdown na zendesk.

        Args:
            ids (dict): Dicionario contendo o nome do campo e ID unico do campo dropdown.

        Returns:
            dict: Dicionario contendo o nome do campo e o valor dentro do dropdown que está selecionado.
        """
        valores_campos = {}
        for nome_campo, id_campo in ids.items():
            try:
                seletor = f'[data-test-id="ticket-form-field-dropdown-field-{id_campo}"] [data-garden-id="typography.ellipsis"]'
                for _ in range(15):
                    elemento = self.driver.find_element(By.CSS_SELECTOR, seletor)
                    valor_elemento = elemento.text
                    if valor_elemento != '-':
                        valores_campos[nome_campo] = valor_elemento
                        break
                    sleep(1)
                else:
                    valores_campos[nome_campo] = valor_elemento

            except NoSuchElementException:
                print(f"Aviso: Campo com seletor '{seletor}' não foi encontrado.")
                valores_campos[nome_campo] = None
        return valores_campos
        
    def preencher_input(self, id:int, valor:str):
        """Preenche com um valor o campo input pre-determinado.

        Args:
            id (int): ID unico do custom field da zendesk.
            valor (str): Valor a ser preenchido no campo.

        Returns:
            None em caso de falha ao não conseguir localizar o elemento.
        """
        seletor = f'.custom_field_{id} input[data-test-id="ticket-fields-text-field"]'
        try:
            campo = self.driver.find_element(By.CSS_SELECTOR, seletor)
            campo.send_keys(valor)

        except NoSuchElementException:
            print(f"Aviso: Campo com seletor '{seletor}' não foi encontrado.")
            return None
        
    def enviar_ticket(self, status:str):
        """Encerra o ticket enviando ele como resolvido, aberto, pendente ou em espera.

        Args:
            status (str): Nome do status que o ticket precisa ser enviado.
        """
        actions = ActionChains(self.driver)
        status_ajustado = status.lower()
        if status_ajustado == 'aberto':
            actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('o').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()
        elif status_ajustado == 'resolvido':
            actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('s').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()
        elif status_ajustado == 'espera' or status_ajustado == 'em espera':
            actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('d').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()
        elif status_ajustado == 'pendente':
            actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('p').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()
        elif status_ajustado == 'mesmo' or status_ajustado == 'mesmo status':
            actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('u').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()

    def fechar_ticket_atual(self):
        """Fecha o ticket atual para evitar cacheamento no driver durante as iterações em cada ticket."""
        for i in range(3):
            try:
                fechar_ticket = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-test-id="close-button"]'))
                )
                fechar_ticket.click()
                # fechar_aba = WebDriverWait(self.driver,10).until(
                #     EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-test-id="ticket-close-confirm-modal-confirm-btn"]'))
                # )
                # fechar_aba.click()
                print('Ticket fechado.')
                break
            except (StaleElementReferenceException, TimeoutException):
                print(f"Tentando fechar ticket.. Elemento obsoleto... (tentativa {i+1})")
                sleep(2)
    
    def esperar_carregamento(self):
        """Ao enviar o ticket como aberto ou outro status, é necessario aguardar o carregamento do ticket para fecha-lo."""
        try:
            seletor_de_carregamento = (By.CSS_SELECTOR, "section.main_panes.ticket.working")
            
            WebDriverWait(self.driver, 5).until(
                EC.presence_of_element_located(seletor_de_carregamento)
            )
            WebDriverWait(self.driver, TIMEOUT).until(
                EC.invisibility_of_element_located(seletor_de_carregamento)
            )
            sleep(2)

        except TimeoutException:
            print("Nenhum círculo de carregamento detectado, continuando...")
            pass
    
    def enviar_mensagem(self, mensagem: str, publica=False):
        """Envia mensagem dentro do ticket como obs. interna ou pública.

        Args:
            mensagem (str): Mensagem a ser enviada no ticket.
            publica (bool, optional): True para enviar mensagem ao cliente, False para adicionar observação interna, o padrão é False.
        """
        try:
            actions = ActionChains(self.driver)
            if publica:
                actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('c').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()
                try:
                    sleep(2)
                    ok = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//button[text()='OK']")))
                    ok.click()
                except:
                    ...
            else:
                actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('x').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()
                
            caixa_texto = WebDriverWait(self.driver, 30).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-test-id="omnicomposer-rich-text-ckeditor"]'))
            )
            caixa_texto.send_keys(mensagem)

        except Exception as e:
            print(e)

    def status_ticket(self):
        """Método para obter o status atual do ticket."""
        try:
            status_locator = (By.CSS_SELECTOR, '[data-test-id="tabs-section-nav-item-ticket"] .ticket_status_label')
            status_element = WebDriverWait(self.driver, 30).until(
                EC.visibility_of_element_located(status_locator)
            )
            status_text = status_element.text
            return status_text
        except:
            print('Erro ao obter o status do ticket.')
            return 'Resolvido'

    def aplicar_macro(self, macro:str):
        """Aplica uma macro no ticket.

        Args:
            macro (str): Caminho da macro que consta no zendesk (ATENÇÃO: Necessario ser exatamente caminho da macro com :: e NÃO o nome dela )
        """
        try:
            actions = ActionChains(self.driver)
            actions.key_down(Keys.CONTROL).key_down(Keys.ALT).send_keys('m').key_up(Keys.ALT).key_up(Keys.CONTROL).perform()

            input_macro = WebDriverWait(self.driver, 3).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "[data-test-id='ticket-footer-macro-menu-autocomplete-input'] input"))
            )
            input_macro.send_keys(macro)
            sleep(4)
            actions.send_keys(Keys.ENTER).perform()
        except Exception as e:
            print(e)