import os
import time
import pandas as pd
from bs4 import BeautifulSoup

from selenium import webdriver
import chromedriver_binary  # do not remove
from selenium.common.exceptions import NoSuchElementException

from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from src.selenium_config import configure_driver


class AnyEc:
    """ Use with WebDriverWait to combine expected_conditions
        in an OR.
    """

    def __init__(self, *args):
        self.ecs = args

    def __call__(self, driver):
        for fn in self.ecs:
            try:
                if fn(driver): return True
            except:
                pass


class CrawlerCei():

    def __init__(self, headless=False, directory=None, debug=False):
        self.BASE_URL = 'https://cei.b3.com.br/'
        self.driver = configure_driver(headless)
        self.directory = directory
        self.debug = debug
        self.__colunas_df_cei = ['Data do Negócio', 'Compra/Venda', 'Mercado', 'Prazo/Vencimento', 'Código Negociação',
               'Especificação do Ativo', 'Quantidade', 'Preço (R$)', 'Valor Total(R$)', 'Fator de Cotação']
        self.id_tabela_negociacao_ativos = 'ctl00_ContentPlaceHolder1_rptAgenteBolsa_ctl00_rptContaBolsa_ctl00_pnAtivosNegociados'
        self.id_mensagem_de_aviso = 'CEIMessageDIV'
        self.id_selecao_corretoras = 'ctl00_ContentPlaceHolder1_ddlAgentes'
        self.id_btn_consultar = 'ctl00_ContentPlaceHolder1_btnConsultar'

    def busca_trades(self):
        try:
            self.driver.get(self.BASE_URL)
            self.__login()
            df = self.__abre_consulta_trades()
            return self.__converte_dataframe_para_formato_padrao(df)
        except Exception as ex:
            raise ex
        finally:
            self.driver.quit()

    def __login(self):
        if self.debug: self.driver.save_screenshot(self.directory + r'01.png')
        txt_login = self.driver.find_element_by_id('ctl00_ContentPlaceHolder1_txtLogin')
        txt_login.clear()
        txt_login.send_keys(os.environ['CPF'])

        time.sleep(3.0)
        txt_senha = self.driver.find_element_by_id('ctl00_ContentPlaceHolder1_txtSenha')
        txt_senha.clear()
        txt_senha.send_keys(os.environ['SENHA_CEI'])
        time.sleep(3.0)

        if self.debug: self.driver.save_screenshot(self.directory + r'02.png')

        btn_logar = self.driver.find_element_by_id('ctl00_ContentPlaceHolder1_btnLogar')
        btn_logar.click()

        try:
            WebDriverWait(self.driver, 60).until(EC.visibility_of_element_located((By.ID, 'objGrafPosiInv')))
        except Exception as ex:
            raise Exception('Nao foi possivel logar no CEI. Possivelmente usuario/senha errada ou indisponibilidade do site')

        if self.debug: self.driver.save_screenshot(self.directory + r'03.png')

    def __abre_consulta_trades(self):

        def consultar_click(driver):
            btn_consultar = WebDriverWait(driver, 20).until(
                EC.element_to_be_clickable((By.ID, self.id_btn_consultar)))
            btn_consultar.click();

        def exists_and_not_disabled(id):
            def until_fn(driver):
                try:
                    driver.find_element_by_id(id)
                except NoSuchElementException:
                    return False
                return driver.find_element_by_id(id).get_attribute("disabled") is None

            return until_fn

        dfs_to_concat = []

        self.driver.get(self.BASE_URL + 'negociacao-de-ativos.aspx')
        if self.debug: self.driver.save_screenshot(self.directory + r'04.png')

        from selenium.webdriver.support.select import Select
        ddlAgentes = Select(self.driver.find_element_by_id(self.id_selecao_corretoras))

        def __busca_trades_de_uma_corretora(i):
            if self.debug: self.driver.save_screenshot(self.directory + r'04_01.png')
            ddlAgentes = Select(self.driver.find_element_by_id(self.id_selecao_corretoras))
            ddlAgentes.select_by_index(i)
            time.sleep(10)
            if self.debug: self.driver.save_screenshot(self.directory + r'04_02.png')
            WebDriverWait(self.driver, 15).until(exists_and_not_disabled(self.id_btn_consultar))
            consultar_click(self.driver)

            if self.debug: self.driver.save_screenshot(self.directory + r'05.png')

            try:
                WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(
                    (By.ID, self.id_mensagem_de_aviso)))
            except:
                pass

            if self.debug: self.driver.save_screenshot(self.directory + r'06.png')

            # checa se existem trades para essa corretora
            aviso = self.driver.find_element_by_id(self.id_mensagem_de_aviso)
            if aviso.text == 'Não foram encontrados resultados para esta pesquisa.\n×':
                consultar_click(self.driver)
                WebDriverWait(self.driver, 60).until(exists_and_not_disabled(self.id_selecao_corretoras))
            else:
                if not exists_and_not_disabled(self.id_tabela_negociacao_ativos)(self.driver):
                    consultar_click(self.driver)

                WebDriverWait(self.driver, 30).until(EC.visibility_of_element_located(
                    (By.ID, self.id_tabela_negociacao_ativos)))

                dfs_to_concat.append(self.__converte_trades_para_dataframe())
                consultar_click(self.driver)
                WebDriverWait(self.driver, 60).until(exists_and_not_disabled(self.id_selecao_corretoras))

        if len(ddlAgentes.options) == 1:
            __busca_trades_de_uma_corretora(0)
        else:
            for i in range(1, len(ddlAgentes.options)):
                __busca_trades_de_uma_corretora(i)

        if len(dfs_to_concat):
            return pd.concat(dfs_to_concat)
        else:
            return pd.DataFrame(columns=self.__colunas_df_cei)

    def __converte_trades_para_dataframe(self):

        soup = BeautifulSoup(self.driver.page_source, 'html.parser')

        top_div = soup.find('div', {'id': self.id_tabela_negociacao_ativos})

        table = top_div.find(lambda tag: tag.name == 'table')

        df = pd.read_html(str(table), decimal=',', thousands='.')[0]

        df = df.dropna(subset=['Mercado'])
        return df


    def __converte_dataframe_para_formato_padrao(self, df):
        df = df.rename(columns={'Código Negociação': 'ticker',
                                'Compra/Venda': 'operacao',
                                'Quantidade': 'qtd',
                                'Data do Negócio': 'data',
                                'Preço (R$)': 'preco',
                                'Valor Total(R$)': 'valor'})

        from src.stuff import calculate_add

        def formata_compra_venda(operacao):
            if operacao == 'V':
                return 'Venda'
            else:
                return 'Compra'

        def remove_fracionado_ticker(ticker):
            return ticker[:-1] if ticker.endswith('F') else ticker

        df['data'] = pd.to_datetime(df['data'], dayfirst=True)
        df['data'] = df['data'].dt.date
        df['ticker'] = df.apply(lambda row: remove_fracionado_ticker(row.ticker), axis=1)
        df['operacao'] = df.apply(lambda row: formata_compra_venda(row.operacao), axis=1)
        df['qtd_ajustada'] = df.apply(lambda row: calculate_add(row), axis=1)

        df['taxas'] = 0.0
        df['aquisicao_via'] = 'HomeBroker'

        df.drop(columns=['Mercado', 
                         'Prazo/Vencimento', 
                         'Especificação do Ativo',
                         'Fator de Cotação'], inplace=True)
        return df