import asyncio
import random
import secrets
import time
import curl_cffi
from aiolimiter import AsyncLimiter
from curl_cffi.requests import AsyncSession
from rich.padding import Padding
from selectolax.parser import HTMLParser

from rich.console import Console
from rich.table import Table
from rich.live import Live
from rich.layout import Layout
from rich.panel import Panel
from rich.align import Align

console = Console()


class Generator:
    def __init__(self):
        self.live = None
        self.stats = {'t': 0, 'b': 0, 'm': 0, 'c': 0, 'r': 0}
        self.sem = AsyncLimiter(90, 2)
        self.proxy_good = set()
        self.num_ok = []
        self.num_plan = []
        self.num_change = []
        self.num_check = []
        self.errors = set()
        self.status = ""
        self.exceptions = set()
        self.time = 0
        self.total_time = 0

    @staticmethod
    def read_file(file_path):
        """Read lines from a file and return as a list."""
        with open(file_path, "r") as file:
            return [line.strip() for line in file]

    @staticmethod
    def write_file(file_path, data):
        """Read lines from a file and return as a list."""
        with open(file_path, "a") as file:
            for item in data:
                file.write(f"{item}\n")

    @staticmethod
    def format_proxy(proxy):
        """Format a proxy string."""
        # Implementar la función de formateo de proxy aquí
        partes_proxy = proxy.split(':')
        ip_port = partes_proxy[0]
        port = partes_proxy[1]
        username = partes_proxy[2]
        password = partes_proxy[3]
        proxy_formateado = f"{username}:{password}@{ip_port}:{port}"
        return proxy_formateado

    def get_info(self, info):
        parse_html = HTMLParser(info)

        # Buscar el elemento #ValorTotalEdit
        saldo_elements = parse_html.css('#ValorTotalEdit')
        if saldo_elements:
            saldo_text = \
                saldo_elements[0].child.html.strip().replace('$', '').replace('.', '').replace(' ', '').split(',')[0]
            if not saldo_text:
                return "Error Saldo vacio"
            try:
                saldo = int(saldo_text)
                return f'${saldo}' if saldo > 0 else "Saldo en 0"
            except ValueError:
                return "Saldo no válido"

        # Buscar el elemento #msg_error
        error_elements = parse_html.css('#msg_error')
        if error_elements:
            error_text = error_elements[0].child.html.strip()
            if not error_text:
                return "Error Info vacia"
            if "Estas al día en tus pagos. Próxima carga Voz y/o Datos" in error_text:
                return error_text.replace('Estas al día en tus pagos. Próxima carga Voz y/o Datos ', '')
            return error_text
        # Si no se encuentra ningún elemento esperado
        return "Error no se encontro informacion"
    def get_time(self,time):
        total_seconds = float(time)

        # Calcular horas, minutos y segundos
        hours = int(total_seconds // 3600)
        minutes = int((total_seconds % 3600) // 60)
        seconds = int(total_seconds % 60)

        # Generar la cadena de texto
        txt = ""
        if hours > 0:
            txt += f"{hours} h {minutes} m "
        elif minutes > 0:
            txt += f"{minutes} m {seconds} s"
        else:
            txt += f"{seconds} s"

        return txt
    def render_stats(self):
        stats_table = Table(title="Stats", title_style="#FF5555", header_style="#FF5555")
        stats_table.add_column("Type", justify="center", style="#FF5555")
        stats_table.add_column("Value", justify="center", style="#C0C0C0", no_wrap=True)

        for key, value in self.stats.items():
            stats_table.add_row(key, str(value))

        return stats_table

    def render_data(self):
        data_table = Table(title="Data", title_style="#FF5555", header_style="#FF5555")
        data_table.add_column("Type", justify="center", style="#FF5555", no_wrap=True)
        data_table.add_column("Count", justify="center", style="#C0C0C0", no_wrap=True)

        data_table.add_row("OK", str(len(self.num_ok)))
        data_table.add_row("Plan", str(len(self.num_plan)))
        data_table.add_row("Change", str(len(self.num_change)))
        data_table.add_row("Check", str(len(self.num_check)))

        return data_table

    def render_error(self):

        txt = ""
        for i in self.errors:
            txt += f"{i}\n"
        return txt

    def render_excpt(self):

        txt = ""
        for i in self.exceptions:
            txt += f"{i}\n"
        return txt

    def render_ui(self):
        layout = Layout()
        timer = Layout(minimum_size=5, size=5)
        timer.split_row(
            Layout(Panel(str(self.time), title="Time", style="white"), name="time"),
            Layout(Panel(str(self.total_time), title="Total Time", style="white"), name="t_time")
        )
        # Fila superior para Error
        data = Layout(size=10)
        data.split_row(Layout(self.render_stats(), name="stats"),
                       Layout(self.render_data(), name="data"))
        # Fila inferior para Stats, Status, y Data
        bottom_row = Layout()
        bottom_row.split_column(
            data,
            Layout(Panel(Align.center(self.status), title="Status", style="#00FF00", padding=(1, 1),
                         height=5), name="status", size=5),
            timer

        )
        layout.split_row(
            Layout(Panel(Align.center(self.render_error()), style="#FF0000", title="Error"), name="errors"),
            Layout(Panel(Align.center(bottom_row), title="Info", style="#808080"), name="info"),
            Layout(Panel(Align.center(self.render_excpt()), style="#FF0000", title="Exception"), name="except")
        )
        return layout


    def update_status(self, status):
        self.status = status
        self.update_ui()

    def update_ui(self):
        self.live.update(self.render_ui())

    async def get_cookie(self, selected_proxy):
        """Placeholder for the get_cookie function."""
        # Implementar la lógica de get_cookie aquí
        async with AsyncLimiter(80, 2):
            proxy = {
                "http": f"http://{selected_proxy}",
                "https": f"http://{selected_proxy}"
            }
            url = 'https://portalpagos.claro.com.co/phrame.php?action=despliegue_personal&clase=VistasClaroComprobante&metodo=generarComprobante&empresa=claro'
            while True:
                try:
                    async with AsyncSession(impersonate="chrome99_android", proxies=proxy) as s:
                        r = await s.get(url)
                    self.proxy_good.add(selected_proxy)
                    break
                except curl_cffi.requests.errors.RequestsError as e:
                    if "curl: (28)" in str(e):
                        selected_proxy = random.choice(list(self.proxy_good))
                        proxy = {
                            "http": f"http://{selected_proxy}",
                            "https": f"http://{selected_proxy}"
                        }
            return r.cookies

    async def search_data(self, num, cookie, selected_proxy):
        async with self.sem:
            proxy = {
                "http": f"http://{selected_proxy}",
                "https": f"http://{selected_proxy}"
            }
            boundary = "WebKitFormBoundary" + ''.join(
                secrets.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') for _ in range(16))
            header = {'content-type': f'multipart/form-data; boundary=----{boundary}'}
            data = f'------{boundary}\r\nContent-Disposition: form-data; name="FLUJOPAGOGW"\r\n\r\nPFPHM\r\n------{boundary}\r\nContent-Disposition: form-data; name="FLUJOPAGO"\r\n\r\n10002\r\n------{boundary}\r\nContent-Disposition: form-data; name="ex"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="paramLista"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="action"\r\n\r\ndespliegue_personal\r\n------{boundary}\r\nContent-Disposition: form-data; name="campos_borrar"\r\n\r\nCLACO_NUMERO\r\n------{boundary}\r\nContent-Disposition: form-data; name="operacion"\r\n\r\nAdicionar\r\n------{boundary}\r\nContent-Disposition: form-data; name="url_modal"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="id_objeto"\r\n\r\n10002\r\n------{boundary}\r\nContent-Disposition: form-data; name="valor_llave"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="parametros_padre"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="mensaje_error"\r\n\r\nDebe asignar a un responsable para la actividad a seguir\r\n------{boundary}\r\nContent-Disposition: form-data; name="go"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="preguardar"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="confGuardaComo"\r\n\r\nSeguro que desea duplicar este registro\r\n------{boundary}\r\nContent-Disposition: form-data; name="modifi_detalle"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="nombre_campo"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="latitud"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="longitud"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="enviarForm"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="autoguardar"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="guion_encuesta"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="clase"\r\n\r\nvistasclaro\r\n------{boundary}\r\nContent-Disposition: form-data; name="metodo"\r\n\r\nconfirmacion\r\n------{boundary}\r\nContent-Disposition: form-data; name="empresa"\r\n\r\nclaro\r\n------{boundary}\r\nContent-Disposition: form-data; name="NumeroCelular"\r\n\r\n{num}\r\n------{boundary}\r\nContent-Disposition: form-data; name="CLACO_NUMERO"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="NRO_CUENTA"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="TIPO_TRANS"\r\n\r\n2\r\n------{boundary}\r\nContent-Disposition: form-data; name="USRIO_NUMERO"\r\n\r\n1\r\n------{boundary}\r\nContent-Disposition: form-data; name="IPTRANSACCION"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="TIPO_TRANS_ORIG"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="FECHA_INICIO"\r\n\r\n2024-07-24 23:46\r\n------{boundary}\r\nContent-Disposition: form-data; name="NumeroIdentificacion"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="CodigoCliente"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="IdentificacionDeudor"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="OrigenPago"\r\n\r\n3\r\n------{boundary}\r\nContent-Disposition: form-data; name="SALDOPARCIAL"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="g-recaptcha-response"\r\n\r\ \r\n------{boundary}\r\nContent-Disposition: form-data; name="mySubmit_"\r\n\r\nContinuar\r\n------{boundary}--\r\n'
            url = 'https://portalpagos.claro.com.co/phrame.php'
            timeout = 20

            while True:
                try:
                    async with AsyncSession(impersonate="chrome99_android", headers=header, cookies=cookie,
                                            proxies=proxy,
                                            timeout=timeout) as s:
                        r = await s.post(url, data=data)
                    self.proxy_good.add(selected_proxy)
                    break
                except curl_cffi.requests.errors.RequestsError as e:
                    if "curl: (28)" in str(e):
                        cookie = await self.get_cookie(selected_proxy)

                    else:
                        # print(e)
                        continue

            if r.status_code == 200:
                i = self.get_info(r.text)
                if "Error" == i:
                    # print(num, "Error")
                    cok = await self.get_cookie(selected_proxy)
                    return await self.search_data(num, cok, selected_proxy)
                return num, self.get_info(r.text)

    async def search_data2(self, num, selected_proxy):
        """Placeholder for the search_data2 function."""
        # Implementar la lógica de search_data2 aquí
        async with self.sem:
            proxy = {
                "http": f"http://{selected_proxy}",
                "https": f"http://{selected_proxy}"
            }
            boundary = "WebKitFormBoundary" + ''.join(
                secrets.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') for _ in range(16))
            header = {'content-type': f'multipart/form-data; boundary=----{boundary}'}
            url = 'https://portalpagos.claro.com.co/phrame.php?action=despliegue_personal&clase=VistasClaroComprobante&metodo=generarComprobante&empresa=claro'
            url2 = 'https://portalpagos.claro.com.co/phrame.php'
            # Define the data to be sent in the POST request
            data = f'------{boundary}\r\nContent-Disposition: form-data; name="FLUJOPAGOGW"\r\n\r\nPFPHM\r\n------{boundary}\r\nContent-Disposition: form-data; name="FLUJOPAGO"\r\n\r\n10002\r\n------{boundary}\r\nContent-Disposition: form-data; name="ex"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="paramLista"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="action"\r\n\r\ndespliegue_personal\r\n------{boundary}\r\nContent-Disposition: form-data; name="campos_borrar"\r\n\r\nCLACO_NUMERO\r\n------{boundary}\r\nContent-Disposition: form-data; name="operacion"\r\n\r\nAdicionar\r\n------{boundary}\r\nContent-Disposition: form-data; name="url_modal"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="id_objeto"\r\n\r\n10002\r\n------{boundary}\r\nContent-Disposition: form-data; name="valor_llave"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="parametros_padre"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="mensaje_error"\r\n\r\nDebe asignar a un responsable para la actividad a seguir\r\n------{boundary}\r\nContent-Disposition: form-data; name="go"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="preguardar"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="confGuardaComo"\r\n\r\nSeguro que desea duplicar este registro\r\n------{boundary}\r\nContent-Disposition: form-data; name="modifi_detalle"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="nombre_campo"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="latitud"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="longitud"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="enviarForm"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="autoguardar"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="guion_encuesta"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="clase"\r\n\r\nvistasclaro\r\n------{boundary}\r\nContent-Disposition: form-data; name="metodo"\r\n\r\nconfirmacion\r\n------{boundary}\r\nContent-Disposition: form-data; name="empresa"\r\n\r\nclaro\r\n------{boundary}\r\nContent-Disposition: form-data; name="NumeroCelular"\r\n\r\n{num}\r\n------{boundary}\r\nContent-Disposition: form-data; name="CLACO_NUMERO"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="NRO_CUENTA"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="TIPO_TRANS"\r\n\r\n2\r\n------{boundary}\r\nContent-Disposition: form-data; name="USRIO_NUMERO"\r\n\r\n1\r\n------{boundary}\r\nContent-Disposition: form-data; name="IPTRANSACCION"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="TIPO_TRANS_ORIG"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="FECHA_INICIO"\r\n\r\n2024-07-24 23:46\r\n------{boundary}\r\nContent-Disposition: form-data; name="NumeroIdentificacion"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="CodigoCliente"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="IdentificacionDeudor"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="OrigenPago"\r\n\r\n3\r\n------{boundary}\r\nContent-Disposition: form-data; name="SALDOPARCIAL"\r\n\r\n\r\n------{boundary}\r\nContent-Disposition: form-data; name="g-recaptcha-response"\r\n\r\ \r\n------{boundary}\r\nContent-Disposition: form-data; name="mySubmit_"\r\n\r\nContinuar\r\n------{boundary}--\r\n'

            while True:
                try:
                    async with AsyncSession(impersonate="chrome99_android", proxies=proxy) as s:
                        r = await s.get(url)
                        if r.status_code == 200:
                            r = await s.post(url2, data=data, headers=header)
                    break
                except curl_cffi.requests.errors.RequestsError as e:
                    if "28" == str(e.code):
                        self.exceptions.add(f"{num}, timeout")
                        self.update_ui()
                        continue  # Retry on timeout
                    else:
                        self.exceptions.add(str(e.args))
                        self.update_ui()
                        # print(e)
                        continue  # Break on other errors

            if r.status_code == 200:
                info = self.get_info(r.text)
                if "Error" in info:
                    selected_proxy2 = random.choice(list(self.proxy_good))
                    self.errors.add(f"{num} {info}")
                    return await self.search_data2(num, selected_proxy2)
                self.proxy_good.add(selected_proxy)
                return num, info
            else:
                selected_proxy2 = random.choice(list(self.proxy_good))
                return await self.search_data2(num, selected_proxy2)

    async def execute_main(self, num, cok, proxy):
        tasks = [asyncio.create_task(self.search_data2(i, proxy[n % len(proxy)])) for n, i in enumerate(num)]
        count = 0
        nice = 0
        plan = 0
        changed = 0
        error = 0
        num_ok, num_plan, num_change, num_check = [], [], [], []
        for future in asyncio.as_completed(tasks):
            num2, info = await future
            self.update_status(f"Processing: {count}/{len(num)}")

            count += 1
            if "/" in info:
                self.num_plan.append(f"{num2} {info}")
                num_plan.append(f"{num2} {info}")
                plan += 1
            elif "No" in info or "Saldo" in info:
                self.num_change.append(num2)
                num_change.append(num2)
                changed += 1
            elif "$" in info:
                self.num_ok.append(f"{num2} {info.replace('$', '')}")
                num_ok.append(f"{num2} {info.replace('$', '')}")
                nice += 1
            else:

                self.num_check.append(num2)
                num_check.append(num2)
                error += 1
        self.update_status(f"Processing: {count}/{len(num)}")
        self.stats['t'] += count
        self.stats['b'] = nice
        self.stats['m'] = plan
        self.stats['c'] = changed
        self.stats['r'] = error

        self.write_file('file.txt', num_ok)
        self.write_file('file2.txt', num_plan)
        self.write_file('file3.txt', num_change)
        self.write_file('file4.txt', num_check)

    async def process(self):
        num = self.read_file('num.txt')
        proxy_list = self.read_file('proxies.txt')
        #proxies_formateados = [self.format_proxy(proxy) for proxy in proxy_list]
        CHUNK = 500
        sublistas = [num[i:i + CHUNK] for i in range(0, len(num), CHUNK)]
        proxies_formateados = proxy_list
        cok = None
        t1 = time.perf_counter()

        with Live(self.render_ui(), refresh_per_second=1000,screen=True) as live:
            self.live = live
            for sublist in sublistas:
                t2 = time.perf_counter()
                async with asyncio.TaskGroup() as tg:
                    tg.create_task(self.execute_main(sublist, cok, proxies_formateados))

                self.time = self.get_time(time.perf_counter() - t2)

                await asyncio.sleep(0.1)  # Para dar tiempo a actualizar la UI
                self.total_time = self.get_time(time.perf_counter() - t1)
                live.refresh()
                self.errors.clear()
                self.exceptions.clear()
            await asyncio.sleep(2)  # Para dar tiempo a ver la actualización final


# Ejecución del script principal
if __name__ == "__main__":
    processor = Generator()
    asyncio.run(processor.process())
