import io
import zipfile
import datetime
import asyncio

from src.crud.atividades import crud
from sqlalchemy.exc import IntegrityError
from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import HTTPException, status, Request
from src.core.api_responses import build_success_payload, to_schema_dict
from src.schemas.atividades import (
    ActivityCreate,
    ActivityResponse,
    ActivitiesCreateResponse
)

class AtividadeService:

    _FILE_FIELDS = ("arquivo", "arquivos", "file", "files")
    _RAW_LOG_FIELDS = ("log", "logs", "atividade", "atividades")

    @staticmethod
    async def buscar_atividades(
        db: AsyncSession,
        computador_id: Optional[str] = None,
        data_inicio: Optional[datetime.datetime] = None,
        data_fim: Optional[datetime.datetime] = None,
        limit: int = 50,
        skip: int = 0
    ):
        if data_inicio is None and data_fim is None:
            data_inicio = datetime.datetime.now() - datetime.timedelta(days=1)

        atividades = await crud.get_activities(
            db=db,
            computador_id=computador_id,
            dataHora_de=data_inicio,
            dataHora_ate=data_fim,
            limit=limit,
            skip=skip
        )
        return [to_schema_dict(ActivityResponse, a) for a in atividades]

    @staticmethod
    async def criar_atividades(request: Request, db: AsyncSession):
        content_type = (request.headers.get("content-type") or "").lower()
        items: List[ActivityCreate] = []

        if content_type.startswith("multipart/form-data"):
            form = await request.form()
            computador_id = str(form.get("computador_id") or "").strip()
            if not computador_id:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="Campo 'computador_id' é obrigatório"
                )

            files: List = []
            for field_name in AtividadeService._FILE_FIELDS:
                if field_name in form:
                    vals = (
                        form.getlist(field_name)
                        if hasattr(form, "getlist")
                        else [form.get(field_name)]
                    )
                    files.extend([v for v in vals if v is not None])

            raw_logs: List[str] = []
            for raw_field_name in AtividadeService._RAW_LOG_FIELDS:
                if raw_field_name in form:
                    raw_values = (
                        form.getlist(raw_field_name)
                        if hasattr(form, "getlist")
                        else [form.get(raw_field_name)]
                    )
                    for raw_value in raw_values:
                        if raw_value is None:
                            continue
                        raw_text = str(raw_value).strip()
                        if raw_text:
                            raw_logs.extend(
                                AtividadeService._split_logs_by_date(raw_text)
                            )

            if not files and not raw_logs:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail=(
                        "Envie pelo menos um arquivo em 'arquivo|arquivos' "
                        "(também aceita 'file|files') ou conteúdo de log "
                        "em 'log|logs|atividade|atividades'"
                    )
                )

            collected_logs: List[str] = list(raw_logs)
            semaphore = asyncio.Semaphore(2)

            async def process_file(file_obj):
                async with semaphore:
                    filename = getattr(file_obj, "filename", "") or ""
                    lowercase_name = filename.lower()
                    is_zip_file = lowercase_name.endswith(".zip")
                    is_text_file = (
                        lowercase_name.endswith(".txt")
                        or lowercase_name.endswith(".log")
                    )

                    if not is_zip_file and not is_text_file:
                        raise HTTPException(
                            status_code=status.HTTP_400_BAD_REQUEST,
                            detail=(
                                f"Arquivo inválido: {filename}. "
                                "Tipos aceitos: .zip, .txt, .log"
                            )
                        )

                    data_bytes = await file_obj.read()
                    try:
                        if is_zip_file:
                            log_entries = await asyncio.to_thread(
                                AtividadeService._process_zip_file,
                                data_bytes,
                                filename
                            )
                        else:
                            log_entries = await asyncio.to_thread(
                                AtividadeService._process_text_file,
                                data_bytes,
                                filename
                            )
                        return log_entries
                    except zipfile.BadZipFile:
                        raise HTTPException(
                            status_code=status.HTTP_400_BAD_REQUEST,
                            detail=f"Arquivo corrompido: {filename}"
                        )
                    except Exception as e:
                        raise HTTPException(
                            status_code=status.HTTP_400_BAD_REQUEST,
                            detail=f"Erro {filename}: {str(e)}"
                        )

            tasks = [process_file(file_obj) for file_obj in files]
            results = await asyncio.gather(*tasks, return_exceptions=True)
            for result in results:
                if isinstance(result, Exception):
                    raise result
                collected_logs.extend(result)

            if not collected_logs:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="Nenhum log válido encontrado"
                )

            for log in collected_logs:
                try:
                    parsed = await asyncio.to_thread(
                        AtividadeService._parse_activity_log, log
                    )
                    parsed["computador_id"] = computador_id
                    items.append(ActivityCreate(**parsed))
                except Exception:
                    continue

            if not items:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail=(
                        "Nenhuma atividade válida encontrada no payload. "
                        "Verifique o formato das linhas de log e o "
                        "computador_id informado."
                    )
                )

        else:
            raise HTTPException(
                status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
                detail=(
                    "Apenas 'multipart/form-data' com arquivos .zip "
                    "é suportado"
                )
            )

        try:
            inserted = await crud.create_activities(db, items)
        except IntegrityError as ie:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=(
                    "Violação de integridade. Verifique se "
                    "'computador_id' existe na tabela de computadores."
                )
            ) from ie
        data = to_schema_dict(
            ActivitiesCreateResponse, {
                "inserted": inserted
            }
        )
        message = (
            f"{inserted} atividades criadas com sucesso."
        )
        return build_success_payload(data=data, message=message)

    @staticmethod
    def _process_text_file(data_bytes: bytes, filename: str) -> List[str]:
        text = None
        for enc in ("utf-8-sig", "utf-8", "latin-1", "cp1252"):
            try:
                text = data_bytes.decode(enc)
                break
            except Exception:
                continue
        if not text:
            raise ValueError(
                f"Não foi possível decodificar o arquivo texto: {filename}"
            )

        parts = AtividadeService._split_logs_by_date(text)
        if parts:
            return parts

        return [
            line.strip()
            for line in text.splitlines()
            if line.strip() and not line.strip().lstrip().startswith('#')
        ]

    @staticmethod
    def _process_zip_file(data_bytes: bytes, filename: str) -> List[str]:
        """Process ZIP file synchronously in thread pool."""
        zf = zipfile.ZipFile(io.BytesIO(data_bytes))
        file_list = zf.namelist()
        collected_logs = []

        for name in file_list:
            if name.endswith("/"):
                continue
            with zf.open(name) as f:
                raw = f.read()
                text = None
                for enc in ("utf-8-sig", "utf-8", "latin-1", "cp1252"):
                    try:
                        text = raw.decode(enc)
                        break
                    except Exception:
                        continue
                if text:
                    parts = AtividadeService._split_logs_by_date(text)
                    if parts:
                        collected_logs.extend(parts)
                    else:
                        lines = [
                            line.strip()
                            for line in text.splitlines()
                            if (
                                line.strip()
                                and not line.strip()
                                .lstrip()
                                .startswith('#')
                            )
                        ]
                        collected_logs.extend(lines)
        return collected_logs

    @staticmethod
    def _parse_activity_log(log: str) -> dict:
        parts = [p.strip() for p in log.split('|')]
        data: dict = {}
        try:
            if len(parts) > 1 and parts[1]:
                dt_str = parts[1].strip()
                if ' ' in dt_str and 'T' not in dt_str:
                    dt_str = dt_str.replace(' ', 'T', 1)
                if '+' in dt_str:
                    dt_str = dt_str.split('+')[0]
                elif dt_str.rfind('-') > 10:
                    dt_str = dt_str[:dt_str.rfind('-')]
                try:
                    data["data_hora"] = datetime.datetime.fromisoformat(dt_str)
                except ValueError:
                    try:
                        data["data_hora"] = datetime.datetime.strptime(
                            dt_str, "%Y-%m-%d %H:%M:%S")
                    except ValueError:
                        pass

            if len(parts) > 2 and parts[2]:
                data["nome_usuario"] = parts[2]
            if len(parts) > 3 and parts[3]:
                data["evento"] = parts[3]
            if len(parts) > 4 and parts[4]:
                data["processo"] = parts[4]
            if len(parts) > 5 and parts[5]:
                val = parts[5].lower()
                if val in {"true", "false"}:
                    data["eh_navegador"] = val == "true"
            if len(parts) > 6 and parts[6]:
                data["nome_janela"] = parts[6]
            if len(parts) > 7 and parts[7]:
                desc = parts[7].strip()
                invalid_descriptions = {
                    "não se aplica",
                    "nao se aplica",
                    "n/a",
                    "na",
                }
                if desc and desc.lower() not in invalid_descriptions:
                    data["descricao"] = desc
            if len(parts) > 8 and parts[8]:
                dur_str = parts[8].strip()
                if dur_str and ':' in dur_str:
                    try:
                        time_parts = dur_str.split(":")
                        if len(time_parts) == 3:
                            h, m, s = time_parts
                            data["duracao"] = datetime.time(
                                int(h), int(m), int(s)
                            )
                    except ValueError:
                        pass
            if len(parts) > 9 and parts[9]:
                for token in parts[9].split(","):
                    token = token.strip()
                    if token.startswith("pid="):
                        data["pid"] = int(token.split("=", 1)[1])
                    if token.startswith("clicks="):
                        data["cliques"] = int(token.split("=", 1)[1])
            if len(parts) > 10 and parts[10] and "keystrokes=" in parts[10]:
                after = parts[10].split("keystrokes=", 1)[1]
                num = ''.join(ch for ch in after if ch.isdigit())
                if num:
                    data["teclas"] = int(num)
        except Exception:
            pass
        return data

    @staticmethod
    def _split_logs_by_date(logs_string: str) -> List[str]:
        """Split logs by date lines more efficiently."""
        lines = logs_string.splitlines()
        logs = []
        current_log = []
        for line in lines:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            parts = line.split('|')
            if len(parts) >= 3 and len(parts[1].strip()) >= 19:
                if current_log:
                    logs.append('|'.join(current_log))
                current_log = [line]
            else:
                current_log.append(line)
        if current_log:
            logs.append('|'.join(current_log))
        return logs
