import re
import time
import math
import logging
import secrets
import mimetypes
from aiohttp import web
from aiohttp.http_exceptions import BadStatusLine
from WebStreamer.bot import multi_clients, work_loads
from WebStreamer.server.exceptions import FIleNotFound
from WebStreamer import Var, utils, StartTime, __version__, StreamBot
from WebStreamer.utils.render_template import render_page

routes = web.RouteTableDef()

@routes.get("/", allow_head=True)
async def root_route_handler(_):
    return web.json_response(
        {
            "server_status": "running",
            "uptime": utils.get_readable_time(time.time() - StartTime),
            "telegram_bot": "@" + StreamBot.username,
            "connected_bots": len(multi_clients),
            "loads": dict(
                ("bot" + str(c + 1), l)
                for c, (_, l) in enumerate(
                    sorted(work_loads.items(), key=lambda x: x[1], reverse=True)
                )
            ),
            "version": __version__,
        }
    )

@routes.get(r"/watch/{path:\d+}", allow_head=True)
async def stream_handler(request: web.Request):
    try:
        path = request.match_info["path"]
        message_id = int(path)
        return web.Response(text=await render_page(message_id), content_type='text/html')
    except FIleNotFound as e:
        raise web.HTTPNotFound(text=e.message)
    except (AttributeError, BadStatusLine, ConnectionResetError):
        pass
    except Exception as e:
        logging.critical(e.with_traceback(None))
        raise web.HTTPInternalServerError(text=str(e))

@routes.get(r"/{path:\d+}", allow_head=True)
async def stream_handler(request: web.Request):
    try:
        path = request.match_info["path"]
        message_id = int(path)
        return await media_streamer(request, message_id)
    except FIleNotFound as e:
        raise web.HTTPNotFound(text=e.message)
    except (AttributeError, BadStatusLine, ConnectionResetError):
        pass
    except Exception as e:
        logging.critical(e.with_traceback(None))
        raise web.HTTPInternalServerError(text=str(e))

class_cache = {}

async def media_streamer(request: web.Request, message_id: int):
    try:
        range_header = request.headers.get("Range", "")
        
        # Selecionar cliente disponível
        index = min(work_loads, key=work_loads.get)
        faster_client = multi_clients[index]
        
        if Var.MULTI_CLIENT:
            logging.info(f"Client {index} is now serving {request.remote}")

        # Obter ou criar instância do streamer
        tg_connect = class_cache.get(faster_client)
        if not tg_connect:
            tg_connect = utils.ByteStreamer(faster_client)
            class_cache[faster_client] = tg_connect

        # Obter metadados do arquivo
        file_id = await tg_connect.get_file_properties(message_id)
        file_size = file_id.file_size

        # Tratamento robusto do range header
        if range_header:
            try:
                # Extrair e validar valores do range
                range_data = range_header.replace("bytes=", "").split("-")
                if len(range_data) != 2:
                    raise ValueError("Formato de range inválido")
                
                from_bytes = int(range_data[0]) if range_data[0] else 0
                until_bytes = int(range_data[1]) if range_data[1] else file_size - 1
                
                # Validação completa do intervalo
                if from_bytes < 0:
                    raise ValueError("Posição inicial não pode ser negativa")
                
                if until_bytes >= file_size:
                    until_bytes = file_size - 1
                
                if from_bytes > until_bytes:
                    # Caso especial: corrigir quando from_bytes == until_bytes + 1
                    if from_bytes == until_bytes + 1 and from_bytes == file_size:
                        from_bytes = until_bytes  # Ajuste para último byte
                    else:
                        raise ValueError("Posição inicial maior que posição final")
                
            except ValueError as e:
                logging.warning(f"Range header inválido: {range_header} - {str(e)}")
                return web.Response(
                    status=400,
                    text=f"Range header inválido: {str(e)}. Formato esperado: bytes=inicio-fim"
                )
        else:
            from_bytes = 0
            until_bytes = file_size - 1

        # Cálculo seguro do tamanho da requisição
        req_length = until_bytes - from_bytes + 1
        
        # Configuração segura do chunk size
        try:
            new_chunk_size = max(await utils.chunk_size(req_length), 1024)  # Mínimo 1KB
        except Exception as e:
            logging.error(f"Erro no cálculo do chunk_size: {str(e)} - Usando padrão 1MB")
            new_chunk_size = 1024 * 1024  # Fallback para 1MB

        # Cálculos protegidos para streaming
        try:
            offset = max(await utils.offset_fix(from_bytes, new_chunk_size), 0)
            first_part_cut = from_bytes - offset
            last_part_cut = (until_bytes % new_chunk_size) + 1
            part_count = math.ceil(req_length / new_chunk_size)
        except Exception as e:
            logging.error(f"Erro nos cálculos de streaming: {str(e)}")
            return web.Response(
                status=500,
                text="Erro interno no processamento do arquivo"
            )

        # Log detalhado para depuração
        logging.debug(
            f"Streaming: {file_id.file_name or 'unnamed'} | "
            f"Size: {file_size} | Range: {from_bytes}-{until_bytes} | "
            f"Req: {req_length} bytes | Chunk: {new_chunk_size} | "
            f"Parts: {part_count}"
        )

        # Gerar stream do arquivo
        try:
            body = tg_connect.yield_file(
                file_id, index, offset, first_part_cut, last_part_cut, part_count, new_chunk_size
            )
        except Exception as e:
            logging.error(f"Falha ao gerar stream: {str(e)}")
            return web.Response(
                status=500,
                text="Falha ao acessar o conteúdo do arquivo"
            )

        # Configurar headers de resposta
        mime_type = file_id.mime_type or mimetypes.guess_type(file_id.file_name or "")[0] or "application/octet-stream"
        file_name = file_id.file_name or f"{secrets.token_hex(4)}.{mime_type.split('/')[-1] if '/' in mime_type else 'bin'}"
        
        headers = {
            "Content-Type": mime_type,
            "Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
            "Content-Disposition": f'inline; filename="{file_name}"',
            "Accept-Ranges": "bytes",
            "Cache-Control": "public, max-age=3600"
        }

        # Retornar resposta apropriada
        if range_header:
            return web.Response(
                status=206,
                body=body,
                headers=headers
            )
        else:
            headers["Content-Length"] = str(file_size)
            return web.Response(
                status=200,
                body=body,
                headers=headers
            )

    except Exception as e:
        logging.critical(f"Erro crítico no media_streamer: {str(e)}", exc_info=True)
        return web.Response(
            status=500,
            text="Erro interno no servidor de streaming"
        )