#!/usr/bin/env python3
"""
TimeCloud MMBackOffice — Automação de Processamento de Assiduidade

Automatiza o processamento diário de dados de assiduidade na aplicação
TimeCloud MMBackOffice. Faz login, navega para "Processar → Processamento",
seleciona os últimos N dias, ativa reprocessamento, e dispara o processamento.

Uso:
    python3 timecloud_process.py              # Execução normal
    python3 timecloud_process.py --dry-run    # Apenas mostra o que faria
"""

import argparse
import logging
import os
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path

from dotenv import load_dotenv
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import (
    TimeoutException,
    NoSuchElementException,
    ElementClickInterceptedException,
    StaleElementReferenceException,
)

try:
    from webdriver_manager.chrome import ChromeDriverManager
    USE_WEBDRIVER_MANAGER = True
except ImportError:
    USE_WEBDRIVER_MANAGER = False

# ---------------------------------------------------------------------------
# Configuração de logging
# ---------------------------------------------------------------------------

LOG_DIR = Path(__file__).parent / "logs"
LOG_DIR.mkdir(exist_ok=True)

log_file = LOG_DIR / f"timecloud_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(log_file, encoding="utf-8"),
        logging.StreamHandler(sys.stdout),
    ],
)
logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constantes — IDs e seletores dos elementos da aplicação
# ---------------------------------------------------------------------------

# Login
SEL_CLOUD_NAME = "txtCloud"
SEL_USERNAME = "txtLogin"
SEL_PASSWORD = "txtPass"
SEL_BTN_LOGIN = "btnEntrar"

# Navegação
SEL_TAB_PROCESSAR = "//span[contains(text(),'Processar')]"
SEL_ICON_PROCESSAMENTO = (
    "//span[contains(text(),'Processamento')]"
    "/ancestor::a[contains(@class,'rtsLink')]"
)

# Processamento
SEL_CHECKBOX_REPROCESSAR = "//input[contains(@id,'chkReprocessarValidados')]"
SEL_BTN_PROCESSAR = "//input[contains(@id,'btnProcessar')]"
SEL_RESUMO = "//textarea[contains(@id,'edtResumo')]"

# Grelha de dias — a tabela principal de meses × dias
SEL_GRID_TABLE = "//table[contains(@id,'grdProcessamento')]"

# Mapeamento de meses para índice de linha na grelha (0-indexed)
MONTH_NAMES = [
    "JAN", "FEV", "MAR", "ABR", "MAI", "JUN",
    "JUL", "AGO", "SET", "OUT", "NOV", "DEZ",
]


# ---------------------------------------------------------------------------
# Funções auxiliares
# ---------------------------------------------------------------------------


def create_driver(headless: bool = True) -> webdriver.Chrome:
    """Cria e retorna uma instância do Chrome WebDriver."""
    options = Options()
    if headless:
        options.add_argument("--headless=new")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--disable-gpu")
    options.add_argument("--window-size=1920,1080")
    # Evitar deteção de automação
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])

    if USE_WEBDRIVER_MANAGER:
        service = Service(ChromeDriverManager().install())
        driver = webdriver.Chrome(service=service, options=options)
    else:
        driver = webdriver.Chrome(options=options)

    driver.implicitly_wait(5)
    return driver


def wait_and_click(driver, xpath: str, timeout: int = 20, description: str = ""):
    """Espera por um elemento e clica nele com retry."""
    desc = description or xpath
    for attempt in range(3):
        try:
            element = WebDriverWait(driver, timeout).until(
                EC.element_to_be_clickable((By.XPATH, xpath))
            )
            driver.execute_script("arguments[0].scrollIntoView(true);", element)
            time.sleep(0.3)
            element.click()
            logger.info(f"✅ Clicado: {desc}")
            return element
        except ElementClickInterceptedException:
            logger.warning(f"⚠️  Clique intercetado em '{desc}', tentativa {attempt + 1}/3")
            time.sleep(1)
        except StaleElementReferenceException:
            logger.warning(f"⚠️  Elemento stale em '{desc}', tentativa {attempt + 1}/3")
            time.sleep(1)
    raise TimeoutException(f"Não consegui clicar em '{desc}' após 3 tentativas")


def wait_for_element(driver, xpath: str, timeout: int = 20):
    """Espera por um elemento e retorna-o."""
    return WebDriverWait(driver, timeout).until(
        EC.presence_of_element_located((By.XPATH, xpath))
    )


# ---------------------------------------------------------------------------
# Passos da automação
# ---------------------------------------------------------------------------


def step_login(driver, url: str, cloud: str, username: str, password: str):
    """Faz login na aplicação TimeCloud."""
    logger.info(f"🔑 A fazer login em {url}...")
    driver.get(url)
    time.sleep(2)

    # Preencher campos
    cloud_input = driver.find_element(By.ID, SEL_CLOUD_NAME)
    cloud_input.clear()
    cloud_input.send_keys(cloud)

    user_input = driver.find_element(By.ID, SEL_USERNAME)
    user_input.clear()
    user_input.send_keys(username)

    pass_input = driver.find_element(By.ID, SEL_PASSWORD)
    pass_input.clear()
    pass_input.send_keys(password)

    # Clicar no botão de login
    btn_login = driver.find_element(By.ID, SEL_BTN_LOGIN)
    btn_login.click()
    logger.info("🔑 Credenciais enviadas, a aguardar login...")

    # Aguardar o carregamento pós-login (desaparece o botão de login)
    try:
        WebDriverWait(driver, 15).until(
            EC.staleness_of(btn_login)
        )
        logger.info("✅ Login efetuado com sucesso!")
    except TimeoutException:
        # Verificar se há mensagem de erro
        try:
            error_el = driver.find_element(By.XPATH, "//span[contains(@class,'error')]")
            raise RuntimeError(f"Erro no login: {error_el.text}")
        except NoSuchElementException:
            logger.warning("⚠️  Não foi possível confirmar o login, a prosseguir...")

    time.sleep(2)


def step_navigate_to_processamento(driver):
    """Navega para a página de Processamento."""
    logger.info("🧭 A navegar para Processar → Processamento...")

    # Clicar no tab "Processar"
    wait_and_click(driver, SEL_TAB_PROCESSAR, description="Tab Processar")
    time.sleep(1)

    # Clicar em "Processamento"
    wait_and_click(driver, SEL_ICON_PROCESSAMENTO, description="Ícone Processamento")
    time.sleep(2)

    logger.info("✅ Navegação para Processamento concluída!")


def step_enable_reprocessamento(driver):
    """Ativa a checkbox 'Reprocessar resultados validados'."""
    logger.info("☑️  A ativar 'Reprocessar resultados validados'...")

    try:
        checkbox = wait_for_element(driver, SEL_CHECKBOX_REPROCESSAR)
        if not checkbox.is_selected():
            driver.execute_script("arguments[0].click();", checkbox)
            logger.info("✅ Checkbox ativada!")
        else:
            logger.info("ℹ️  Checkbox já estava ativada.")
    except TimeoutException:
        logger.warning("⚠️  Checkbox de reprocessamento não encontrada, a prosseguir...")


def compute_date_range(lookback_days: int) -> list[tuple[int, int]]:
    """
    Calcula a lista de datas (mês, dia) a selecionar na grelha.
    Retorna tuplos (mês_0indexed, dia) do mais antigo para o mais recente.
    O range é de (hoje - lookback_days) até ontem (inclusive).
    """
    today = datetime.now().date()
    dates = []
    for i in range(lookback_days, 0, -1):
        d = today - timedelta(days=i)
        dates.append((d.month - 1, d.day))  # mês 0-indexed
    return dates


def step_select_dates(driver, dates: list[tuple[int, int]], dry_run: bool = False):
    """Seleciona as datas na grelha de processamento."""
    logger.info(f"📅 A selecionar {len(dates)} dia(s) na grelha...")

    for month_idx, day in dates:
        month_name = MONTH_NAMES[month_idx]
        logger.info(f"  📌 Selecionando {day} {month_name}...")

        if dry_run:
            logger.info(f"     [DRY-RUN] Saltaria a seleção de {day} {month_name}")
            continue

        # Construir XPath para a célula da grelha
        # A grelha tem linhas por mês e colunas por dia
        # Tentamos várias estratégias de seleção
        cell_xpaths = [
            # Estratégia 1: Procurar na tabela pela linha do mês e célula do dia
            f"{SEL_GRID_TABLE}//tr[td[contains(text(),'{month_name}')]]"
            f"/td[{day + 1}]",
            # Estratégia 2: Linha por índice (header + month_idx)
            f"{SEL_GRID_TABLE}//tr[{month_idx + 2}]/td[{day + 1}]",
        ]

        clicked = False
        for xpath in cell_xpaths:
            try:
                cell = WebDriverWait(driver, 5).until(
                    EC.element_to_be_clickable((By.XPATH, xpath))
                )
                driver.execute_script("arguments[0].scrollIntoView(true);", cell)
                time.sleep(0.2)
                cell.click()
                logger.info(f"     ✅ Célula {day} {month_name} selecionada")
                clicked = True
                break
            except (TimeoutException, NoSuchElementException):
                continue

        if not clicked:
            logger.warning(f"     ⚠️  Não consegui selecionar {day} {month_name}")

    time.sleep(1)


def step_processar(driver, dry_run: bool = False):
    """Clica no botão 'Processar' para iniciar o processamento."""
    if dry_run:
        logger.info("🚀 [DRY-RUN] Saltaria o clique em 'Processar'")
        return

    logger.info("🚀 A clicar em 'Processar'...")
    wait_and_click(driver, SEL_BTN_PROCESSAR, description="Botão Processar")
    logger.info("✅ Processamento iniciado!")


def step_wait_completion(driver, timeout_minutes: int = 30, dry_run: bool = False):
    """Espera pela conclusão do processamento, monitorizando o textarea de resumo."""
    if dry_run:
        logger.info("⏳ [DRY-RUN] Saltaria a espera por conclusão")
        return True

    logger.info(f"⏳ A aguardar conclusão (timeout: {timeout_minutes} min)...")
    start_time = time.time()
    timeout_seconds = timeout_minutes * 60
    last_status = ""

    while time.time() - start_time < timeout_seconds:
        try:
            resumo = driver.find_element(By.XPATH, SEL_RESUMO)
            text = resumo.get_attribute("value") or resumo.text or ""

            # Mostrar progressão (apenas quando muda)
            if text and text != last_status:
                # Mostrar apenas última linha relevante
                lines = [l.strip() for l in text.strip().split("\n") if l.strip()]
                if lines:
                    logger.info(f"  📊 {lines[-1]}")
                last_status = text

            # Verificar conclusão
            # O processamento termina quando o textarea contém indicação de fim
            if any(marker in text.lower() for marker in [
                "processamento concluído",
                "processamento concluido",
                "processo concluído",
                "processo concluido",
                "terminado",
                "... fim",
                "concluído com sucesso",
            ]):
                elapsed = time.time() - start_time
                logger.info(f"✅ Processamento concluído! (Demorou {elapsed:.0f}s)")
                return True

        except (NoSuchElementException, StaleElementReferenceException):
            pass

        time.sleep(5)  # Poll a cada 5 segundos

    elapsed = time.time() - start_time
    logger.error(f"❌ Timeout após {elapsed:.0f}s! O processamento pode ainda estar a decorrer.")
    return False


# ---------------------------------------------------------------------------
# Função principal
# ---------------------------------------------------------------------------


def main():
    parser = argparse.ArgumentParser(
        description="TimeCloud MMBackOffice — Automação de Processamento"
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Apenas mostra o que faria, sem executar ações destrutivas",
    )
    parser.add_argument(
        "--no-headless",
        action="store_true",
        help="Corre o browser em modo visível (para debug)",
    )
    args = parser.parse_args()

    # Carregar variáveis de ambiente
    env_path = Path(__file__).parent / ".env"
    if env_path.exists():
        load_dotenv(env_path)
        logger.info(f"📄 Ficheiro .env carregado de {env_path}")
    else:
        logger.warning("⚠️  Ficheiro .env não encontrado, a usar variáveis de sistema")

    # Ler configuração
    url = os.getenv("TC_URL", "https://software.mmconnect.pt/TimeCloud/MMbackoffice/MMBackOffice.aspx")
    cloud_name = os.getenv("TC_CLOUD_NAME", "Protek")
    username = os.getenv("TC_USERNAME", "")
    password = os.getenv("TC_PASSWORD", "")
    lookback_days = int(os.getenv("TC_LOOKBACK_DAYS", "15"))
    timeout_minutes = int(os.getenv("TC_TIMEOUT_MINUTES", "30"))

    if not username or not password:
        logger.error("❌ TC_USERNAME e TC_PASSWORD são obrigatórios! Configure o ficheiro .env")
        sys.exit(1)

    # Calcular datas
    dates = compute_date_range(lookback_days)
    logger.info(f"📅 Datas a processar ({lookback_days} dias): "
                f"{', '.join(f'{MONTH_NAMES[m]}/{d}' for m, d in dates)}")

    if args.dry_run:
        logger.info("🏳️  MODO DRY-RUN ATIVO — nenhuma ação destrutiva será executada")

    # Iniciar browser
    driver = None
    try:
        headless = not args.no_headless
        logger.info(f"🌐 A iniciar Chrome ({'headless' if headless else 'visível'})...")
        driver = create_driver(headless=headless)

        # 1. Login
        step_login(driver, url, cloud_name, username, password)

        # 2. Navegar para Processamento
        step_navigate_to_processamento(driver)

        # 3. Ativar reprocessamento
        step_enable_reprocessamento(driver)

        # 4. Selecionar datas
        step_select_dates(driver, dates, dry_run=args.dry_run)

        # 5. Processar
        step_processar(driver, dry_run=args.dry_run)

        # 6. Aguardar conclusão
        success = step_wait_completion(driver, timeout_minutes, dry_run=args.dry_run)

        if success:
            logger.info("🎉 Automação concluída com sucesso!")
        else:
            logger.error("💥 Automação terminou com problemas.")
            sys.exit(1)

    except Exception as e:
        logger.error(f"💥 Erro fatal: {e}", exc_info=True)
        # Guardar screenshot para debug
        if driver:
            screenshot_path = LOG_DIR / f"error_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
            try:
                driver.save_screenshot(str(screenshot_path))
                logger.info(f"📸 Screenshot de erro guardado em {screenshot_path}")
            except Exception:
                pass
        sys.exit(1)

    finally:
        if driver:
            driver.quit()
            logger.info("🌐 Browser encerrado.")


if __name__ == "__main__":
    main()
