import requests
import json
import time
import os
import mysql.connector
from datetime import datetime
from datetime import datetime, timedelta

# =============================================
# Constantes de caminhos
# =============================================
LOG_DIR = "logs"  # Pasta de logs permanece para registrar mensagens
SYSTEM_LOG_DIR = os.path.join(LOG_DIR, "system")
ERROR_LOG_DIR = os.path.join(LOG_DIR, "errors")

# Nova pasta para cache dos dados (Full Load / Incremental)
CACHE_DIR = "dados"
ROOMING_CACHE_FILE = os.path.join(CACHE_DIR, "BI-HITS-ultimo_dado_rooming.json")
OCCUPATION_CACHE_FILE = os.path.join(CACHE_DIR, "BI-HITS-ultimo_dado_occupation.json")

# =============================================
# Funções para garantir diretórios
# =============================================
def ensure_dir(path):
    """Cria o diretório 'path' se não existir."""
    if not os.path.exists(path):
        os.makedirs(path)

def ensure_log_dir():
    """
    Garante a existência da estrutura de pastas para os logs:
      logs/
        errors/
        system/
    """
    ensure_dir(LOG_DIR)
    ensure_dir(SYSTEM_LOG_DIR)
    ensure_dir(ERROR_LOG_DIR)

# =============================================
# Funções de Log
# =============================================
def log_system(msg):
    """Registra mensagens gerais do sistema no arquivo de log."""
    ensure_log_dir()
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    full_msg = f"[{timestamp}] [SYSTEM] {msg}"
    print(full_msg)
    with open(os.path.join(SYSTEM_LOG_DIR, "BI-HITS_system_log.txt"), "a", encoding="utf-8") as log_file:
        log_file.write(full_msg + "\n")

def log_update_data(msg):
    """Registra detalhes de registros novos/atualizados."""
    ensure_log_dir()
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    full_msg = f"[{timestamp}] [UPDATE] {msg}"
    print(full_msg)
    with open(os.path.join(SYSTEM_LOG_DIR, "BI-HITS_updated_data_log.txt"), "a", encoding="utf-8") as log_file:
        log_file.write(full_msg + "\n")

def log_error(msg):
    """Registra erros no arquivo de log."""
    ensure_log_dir()
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    full_msg = f"[{timestamp}] [ERROR] {msg}"
    print(full_msg)
    with open(os.path.join(ERROR_LOG_DIR, "BI-HITS_error_log.txt"), "a", encoding="utf-8") as log_file:
        log_file.write(full_msg + "\n")

# =============================================
# Funções de Token e Fetch
# =============================================
def get_cached_token():
    cache_file = "token_cache.json"
    if os.path.exists(cache_file):
        with open(cache_file, "r", encoding="utf-8") as f:
            cache_data = json.load(f)
        if "token" in cache_data and "expires" in cache_data and cache_data["expires"] > time.time():
            log_system("Token recuperado do cache.")
            return cache_data["token"]
    token = get_token()
    if not token:
        raise Exception("Falha ao obter token.")
    with open(cache_file, "w", encoding="utf-8") as f:
        json.dump({"token": token, "expires": time.time() + 14400}, f)
    log_system("Token novo obtido e armazenado no cache.")
    return token

def get_token():
    token_url = "http://localhost/BI-HITS/dados/Hits/token/token.php"
    payload = {
        "username": "seu_usuario",  # Ajuste conforme necessário
        "password": "sua_senha"     # Ajuste conforme necessário
    }
    try:
        response = requests.post(token_url, data=payload, timeout=30)
        response.raise_for_status()
        raw = response.text
        log_system(f"Resposta bruta do token: {raw}")
        prefix = "Token obtido:"
        if raw.startswith(prefix):
            token = raw.replace(prefix, "").strip()
            return token
        else:
            data = response.json()
            return data.get("token")
    except Exception as e:
        log_error(f"Erro ao obter token: {e}")
        return None

def fetch_rooming_nights(page, headers, ini_date, fin_date, size, retries=3, timeout=60):
    url = "https://api.hitspms.net/Datashare/RevenueManagement/RoomingNights"
    params = {
        "IniDate": ini_date,
        "FinDate": fin_date,
        "Page": page,
        "Size": size
    }
    attempt = 0
    while attempt < retries:
        try:
            response = requests.get(url, headers=headers, params=params, timeout=timeout)
            response.raise_for_status()
            result = response.json()
            if isinstance(result, dict) and "data" in result:
                result = result["data"]
            return result
        except Exception as e:
            log_error(f"Erro na RoomingNights - Página {page} tentativa {attempt+1}: {e}")
            attempt += 1
            time.sleep(3)
    return []

def fetch_occupation_inventory(page, headers, ini_date, fin_date, size, retries=3, timeout=60):
    url = "https://api.hitspms.net/Datashare/RevenueManagement/OccupationInventory"
    params = {
        "IniDate": ini_date,
        "FinDate": fin_date,
        "Page": page,
        "Size": size
    }
    attempt = 0
    while attempt < retries:
        try:
            response = requests.get(url, headers=headers, params=params, timeout=timeout)
            response.raise_for_status()
            result = response.json()
            if isinstance(result, dict) and "data" in result:
                result = result["data"]
            return result
        except Exception as e:
            log_error(f"Erro na OccupationInventory - Página {page} tentativa {attempt+1}: {e}")
            attempt += 1
            time.sleep(3)
    return []

# =============================================
# Função para calcular diferenças entre registros
# =============================================
def get_differences(old_record, new_record):
    differences = []
    all_keys = set(old_record.keys()).union(new_record.keys())
    for key in all_keys:
        old_val = old_record.get(key)
        new_val = new_record.get(key)
        if old_val != new_val:
            differences.append(f"{key}: '{old_val}' -> '{new_val}'")
    return ", ".join(differences)

# =============================================
# Função de Comparação dos Dados
# =============================================
def compare_data(api_data, local_data, key_func):
    def relevant_fields(record):
        return {
            "amount": record.get("amount"),
            "roomingNightCount": record.get("roomingNightCount"),
            "mealPlanAmountPaxChd": record.get("mealPlanAmountPaxChd"),
            "reservationStatus": record.get("reservationStatus"),
            "rateId": record.get("rateId"),
            "roomId": record.get("roomId"),
            "endOfDayDate": record.get("endOfDayDate"),
            "reservationStatusName": record.get("reservationStatusName"),
            "feeAmount": record.get("feeAmount"),
            "originNumber": record.get("originNumber"),
            "reservationCreatedAt": record.get("reservationCreatedAt"),
        }

    local_dict = { key_func(item): relevant_fields(item) for item in local_data }
    updates = []
    new_records = []

    for record in api_data:
        key = key_func(record)
        current_fields = relevant_fields(record)

        if key in local_dict:
            if local_dict[key] != current_fields:
                diff_fields = []
                for k in current_fields:
                    if current_fields[k] != local_dict[key].get(k):
                        diff_fields.append(f"{k}: {local_dict[key].get(k)} → {current_fields[k]}")
                record["diff"] = ", ".join(diff_fields)
                updates.append(record)
        else:
            new_records.append(record)

    return {"updates": updates, "new": new_records}




# =============================================
# Funções para obter chave única
# =============================================
# 1) Use a mesma chave que você usa no banco
def rooming_key(rec):
    return f"{rec['id']}_{rec['endOfDayDate'][:10]}"   # id + AAAA-MM-DD


def occupation_key(rec):
    return f"{rec['propertyId']}_{rec['date'][:10]}"


# =============================================
# Funções para truncar (apagar) os dados do banco
# =============================================
def truncate_rooming_nights():
    try:
        conn = mysql.connector.connect(host="localhost", user="root", password="", database="bihits")
        cursor = conn.cursor()
        cursor.execute("TRUNCATE TABLE RoomingNights")
        conn.commit()
        log_system("RoomingNights - Tabela truncada com sucesso.")
    except Exception as e:
        log_error(f"Erro truncando RoomingNights: {e}")
    finally:
        cursor.close()
        conn.close()

def truncate_occupation_inventory():
    try:
        conn = mysql.connector.connect(host="localhost", user="root", password="", database="bihits")
        cursor = conn.cursor()
        
        # Primeiro deleta os registros filhos
        cursor.execute("DELETE FROM OccupationInventoryRoomTypes")
        conn.commit()
        
        # Depois deleta os registros pais
        cursor.execute("DELETE FROM OccupationInventory")
        conn.commit()
        
        log_system("OccupationInventory e OccupationInventoryRoomTypes - Registros deletados com sucesso.")
    except Exception as e:
        log_error(f"Erro deletando OccupationInventory: {e}")
    finally:
        cursor.close()
        conn.close()


# =============================================
# Upsert para RoomingNights
# =============================================
def upsert_rooming_nights(records):
    try:
        conn = mysql.connector.connect(host="localhost", user="root", password="", database="bihits")
    except Exception as e:
        log_error(f"Erro ao conectar com MySQL (RoomingNights): {e}")
        return

    cursor = conn.cursor()
    
    sql = """
    INSERT INTO RoomingNights (
        id, propertyId, originId, type, roomId, pax, chd, chdAge1, chdAge2, chdAge3, chdAge4, age5,
        amount, endOfDayDate, date, dailyType, roomingNightCount, roomTypeId, version, requesterCity,
        companyId, companyName, companyTravelAgent, companyTravelAgentId, companyTravelAgentName,
        requesterCountry, guestId, guestName, guestMail, guestPhone, guestCellPhone, mealPlanType,
        mealPlanAmountPaxChd, qtyAgeNotDefined, qtyEightyOneToNinety, qtyElevenToFifteen, qtyFemale,
        qtyFiftyOneToSixty, qtyFortyOneToFifty, qtyFromZeroToTen, qtyGenderNotDefined, qtyGreaterThanNinety,
        qtyMale, qtySeventyOneToEighty, qtySixteenToTwenty, qtySixtyOneToSeventy, qtyThirtyOneToForty,
        qtyTwentyOneToThirty, rateId, rateName, ratePlanId, ratePlanName, requesterState, requesterId,
        requesterName, requesterTravelAgent, reservationChannelId, reservationChannelName, roomCode,
        roomTypeName, companyCity, companyCountry, companyState, guestCity, guestCountry, guestState,
        propertyName, managerId, managerName, marketSegmentId, marketSegmentName, purposeStay, purposeStayId,
        sourceChannelId, sourceChannelName, dateAdd, companyMainDoc, companyMainDocType, dailyMonth, dailyYear,
        `group`, groupName, guestMainDoc, guestMainDocType, originNumber, originType, requesterMainDoc,
        requesterMainDocType, reservationCreatedAt, reservationCreatedUser, reservationCreatedUserId,
        dateCheckin, dateCheckout, voucher, reservationStatus, reservationStatusName, feeAmount,
        chdRange1, chdRange2, chdRange3
    ) VALUES (
        %(id)s, %(propertyId)s, %(originId)s, %(type)s, %(roomId)s, %(pax)s, %(chd)s, %(chdAge1)s, %(chdAge2)s,
        %(chdAge3)s, %(chdAge4)s, %(age5)s, %(amount)s, %(endOfDayDate)s, %(date)s, %(dailyType)s,
        %(roomingNightCount)s, %(roomTypeId)s, %(version)s, %(requesterCity)s, %(companyId)s, %(companyName)s,
        %(companyTravelAgent)s, %(companyTravelAgentId)s, %(companyTravelAgentName)s, %(requesterCountry)s,
        %(guestId)s, %(guestName)s, %(guestMail)s, %(guestPhone)s, %(guestCellPhone)s, %(mealPlanType)s,
        %(mealPlanAmountPaxChd)s, %(qtyAgeNotDefined)s, %(qtyEightyOneToNinety)s, %(qtyElevenToFifteen)s,
        %(qtyFemale)s, %(qtyFiftyOneToSixty)s, %(qtyFortyOneToFifty)s, %(qtyFromZeroToTen)s, %(qtyGenderNotDefined)s,
        %(qtyGreaterThanNinety)s, %(qtyMale)s, %(qtySeventyOneToEighty)s, %(qtySixteenToTwenty)s,
        %(qtySixtyOneToSeventy)s, %(qtyThirtyOneToForty)s, %(qtyTwentyOneToThirty)s, %(rateId)s, %(rateName)s,
        %(ratePlanId)s, %(ratePlanName)s, %(requesterState)s, %(requesterId)s, %(requesterName)s,
        %(requesterTravelAgent)s, %(reservationChannelId)s, %(reservationChannelName)s, %(roomCode)s,
        %(roomTypeName)s, %(companyCity)s, %(companyCountry)s, %(companyState)s, %(guestCity)s,
        %(guestCountry)s, %(guestState)s, %(propertyName)s, %(managerId)s, %(managerName)s, %(marketSegmentId)s,
        %(marketSegmentName)s, %(purposeStay)s, %(purposeStayId)s, %(sourceChannelId)s, %(sourceChannelName)s,
        %(dateAdd)s, %(companyMainDoc)s, %(companyMainDocType)s, %(dailyMonth)s, %(dailyYear)s, %(group)s,
        %(groupName)s, %(guestMainDoc)s, %(guestMainDocType)s, %(originNumber)s, %(originType)s,
        %(requesterMainDoc)s, %(requesterMainDocType)s, %(reservationCreatedAt)s, %(reservationCreatedUser)s,
        %(reservationCreatedUserId)s, %(dateCheckin)s, %(dateCheckout)s, %(voucher)s, %(reservationStatus)s,
        %(reservationStatusName)s, %(feeAmount)s, %(chdRange1)s, %(chdRange2)s, %(chdRange3)s
    )
    ON DUPLICATE KEY UPDATE
        propertyId = VALUES(propertyId),
        originId = VALUES(originId),
        type = VALUES(type),
        roomId = VALUES(roomId),
        pax = VALUES(pax),
        chd = VALUES(chd),
        chdAge1 = VALUES(chdAge1),
        chdAge2 = VALUES(chdAge2),
        chdAge3 = VALUES(chdAge3),
        chdAge4 = VALUES(chdAge4),
        age5 = VALUES(age5),
        amount = VALUES(amount),
        endOfDayDate = VALUES(endOfDayDate),
        date = VALUES(date),
        dailyType = VALUES(dailyType),
        roomingNightCount = VALUES(roomingNightCount),
        roomTypeId = VALUES(roomTypeId),
        version = VALUES(version),
        requesterCity = VALUES(requesterCity),
        companyId = VALUES(companyId),
        companyName = VALUES(companyName),
        companyTravelAgent = VALUES(companyTravelAgent),
        companyTravelAgentId = VALUES(companyTravelAgentId),
        companyTravelAgentName = VALUES(companyTravelAgentName),
        requesterCountry = VALUES(requesterCountry),
        guestId = VALUES(guestId),
        guestName = VALUES(guestName),
        guestMail = VALUES(guestMail),
        guestPhone = VALUES(guestPhone),
        guestCellPhone = VALUES(guestCellPhone),
        mealPlanType = VALUES(mealPlanType),
        mealPlanAmountPaxChd = VALUES(mealPlanAmountPaxChd),
        qtyAgeNotDefined = VALUES(qtyAgeNotDefined),
        qtyEightyOneToNinety = VALUES(qtyEightyOneToNinety),
        qtyElevenToFifteen = VALUES(qtyElevenToFifteen),
        qtyFemale = VALUES(qtyFemale),
        qtyFiftyOneToSixty = VALUES(qtyFiftyOneToSixty),
        qtyFortyOneToFifty = VALUES(qtyFortyOneToFifty),
        qtyFromZeroToTen = VALUES(qtyFromZeroToTen),
        qtyGenderNotDefined = VALUES(qtyGenderNotDefined),
        qtyGreaterThanNinety = VALUES(qtyGreaterThanNinety),
        qtyMale = VALUES(qtyMale),
        qtySeventyOneToEighty = VALUES(qtySeventyOneToEighty),
        qtySixteenToTwenty = VALUES(qtySixteenToTwenty),
        qtySixtyOneToSeventy = VALUES(qtySixtyOneToSeventy),
        qtyThirtyOneToForty = VALUES(qtyThirtyOneToForty),
        qtyTwentyOneToThirty = VALUES(qtyTwentyOneToThirty),
        rateId = VALUES(rateId),
        rateName = VALUES(rateName),
        ratePlanId = VALUES(ratePlanId),
        ratePlanName = VALUES(ratePlanName),
        requesterState = VALUES(requesterState),
        requesterId = VALUES(requesterId),
        requesterName = VALUES(requesterName),
        requesterTravelAgent = VALUES(requesterTravelAgent),
        reservationChannelId = VALUES(reservationChannelId),
        reservationChannelName = VALUES(reservationChannelName),
        roomCode = VALUES(roomCode),
        roomTypeName = VALUES(roomTypeName),
        companyCity = VALUES(companyCity),
        companyCountry = VALUES(companyCountry),
        companyState = VALUES(companyState),
        guestCity = VALUES(guestCity),
        guestCountry = VALUES(guestCountry),
        guestState = VALUES(guestState),
        propertyName = VALUES(propertyName),
        managerId = VALUES(managerId),
        managerName = VALUES(managerName),
        marketSegmentId = VALUES(marketSegmentId),
        marketSegmentName = VALUES(marketSegmentName),
        purposeStay = VALUES(purposeStay),
        purposeStayId = VALUES(purposeStayId),
        sourceChannelId = VALUES(sourceChannelId),
        sourceChannelName = VALUES(sourceChannelName),
        dateAdd = VALUES(dateAdd),
        companyMainDoc = VALUES(companyMainDoc),
        companyMainDocType = VALUES(companyMainDocType),
        dailyMonth = VALUES(dailyMonth),
        dailyYear = VALUES(dailyYear),
        `group` = VALUES(`group`),
        groupName = VALUES(groupName),
        guestMainDoc = VALUES(guestMainDoc),
        guestMainDocType = VALUES(guestMainDocType),
        originNumber = VALUES(originNumber),
        originType = VALUES(originType),
        requesterMainDoc = VALUES(requesterMainDoc),
        requesterMainDocType = VALUES(requesterMainDocType),
        reservationCreatedAt = VALUES(reservationCreatedAt),
        reservationCreatedUser = VALUES(reservationCreatedUser),
        reservationCreatedUserId = VALUES(reservationCreatedUserId),
        dateCheckin = VALUES(dateCheckin),
        dateCheckout = VALUES(dateCheckout),
        voucher = VALUES(voucher),
        reservationStatus = VALUES(reservationStatus),
        reservationStatusName = VALUES(reservationStatusName),
        feeAmount = VALUES(feeAmount),
        chdRange1 = VALUES(chdRange1),
        chdRange2 = VALUES(chdRange2),
        chdRange3 = VALUES(chdRange3)
    """
    
    for record in records:
        try:
            cursor.execute(sql, record)
            log_system(f"Upsert RoomingNight (ID: {record.get('id')}) OK")
        except Exception as e:
            log_error(f"Erro ao processar RoomingNight (ID: {record.get('id')}): {e}")
    
    conn.commit()
    cursor.close()
    conn.close()
    log_system("Upsert RoomingNights concluído. Registros processados: " + str(len(records)))

# =============================================
# Upsert para OccupationInventory e RoomTypes
# =============================================
def upsert_occupation_inventory(records):
    try:
        conn = mysql.connector.connect(host="localhost", user="root", password="", database="bihits")
        cursor = conn.cursor()
    except Exception as e:
        log_error(f"Erro ao conectar com o MySQL (OccupationInventory): {e}")
        return

    inv_sql = """
    INSERT INTO OccupationInventory (propertyId, date, totalRoom, occ, outOfOrder, avail)
    VALUES (%(propertyId)s, %(date)s, %(totalRoom)s, %(occ)s, %(outOfOrder)s, %(avail)s)
    ON DUPLICATE KEY UPDATE
        totalRoom = VALUES(totalRoom),
        occ = VALUES(occ),
        outOfOrder = VALUES(outOfOrder),
        avail = VALUES(avail),
        id = LAST_INSERT_ID(id)
    """
    room_sql = """
    INSERT INTO OccupationInventoryRoomTypes (
        occupationInventoryId, roomTypeId, roomTypeName, totalRoomType,
        occRoomType, availRoomType, outOfOrderRoomType
    ) VALUES (
        %(occupationInventoryId)s, %(roomTypeId)s, %(roomTypeName)s,
        %(totalRoomType)s, %(occRoomType)s, %(availRoomType)s, %(outOfOrderRoomType)s
    )
    ON DUPLICATE KEY UPDATE
        roomTypeName = VALUES(roomTypeName),
        totalRoomType = VALUES(totalRoomType),
        occRoomType = VALUES(occRoomType),
        availRoomType = VALUES(availRoomType),
        outOfOrderRoomType = VALUES(outOfOrderRoomType)
    """

    processed_count = 0
    for record in records:
        inv_data = {
            "propertyId": record.get("propertyId"),
            "date": record.get("date"),
            "totalRoom": record.get("totalRoom"),
            "occ": record.get("occ"),
            "outOfOrder": record.get("outOfOrder"),
            "avail": record.get("avail")
        }
        try:
            cursor.execute(inv_sql, inv_data)
            conn.commit()

            # Agora busca o ID correto (se foi insert ou update)
            cursor.execute("SELECT id FROM OccupationInventory WHERE propertyId = %s AND date = %s", (record.get("propertyId"), record.get("date")))
            inv_id_row = cursor.fetchone()
            inv_id = inv_id_row[0] if inv_id_row else None
            rcount = cursor.rowcount
            action = "Inserido" if rcount == 1 else "Atualizado" if rcount > 1 else "Sem alteração"
            log_system(f"OccupationInventory {record.get('propertyId')}_{record.get('date')} -> {action} (rowcount={rcount})")
        except Exception as e:
            log_error(f"Erro no upsert de OccupationInventory ({record.get('propertyId')}_{record.get('date')}): {e}")
            continue

        # Detalha cada roomType com rowcount
        for rt in record.get("roomTypes", []):
            rt_data = {
                "occupationInventoryId": inv_id,
                "roomTypeId": rt.get("roomTypeId"),
                "roomTypeName": rt.get("roomTypeName"),
                "totalRoomType": rt.get("totalRoomType"),
                "occRoomType": rt.get("occRoomType"),
                "availRoomType": rt.get("availRoomType"),
                "outOfOrderRoomType": rt.get("outOfOrderRoomType")
            }
            try:
                cursor.execute(room_sql, rt_data)
                rt_rcount = cursor.rowcount
                rt_action = "Inserido" if rt_rcount == 1 else "Atualizado" if rt_rcount > 1 else "Sem alteração"
                log_system(f"OccupationInventoryRoomTypes InvID {inv_id}, roomTypeId {rt.get('roomTypeId')} -> {rt_action} (rowcount={rt_rcount})")
            except Exception as e:
                log_error(f"Erro no upsert de RoomType (InvID {inv_id}, roomTypeId {rt.get('roomTypeId')}): {e}")

        processed_count += 1

    conn.commit()
    cursor.close()
    conn.close()
    log_system(f"Upsert OccupationInventory concluído. Registros processados: {processed_count}")


# =============================================
# Salvando os registros em um arquivo de cache (JSON)
# =============================================
def save_cache_file(data, cache_file, category):
    try:
        ensure_dir(CACHE_DIR)
        with open(cache_file, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=4)
        log_system(f"{category} - Cache criado/atualizado: {cache_file}")
    except Exception as e:
        log_error(f"Erro ao salvar cache de {category}: {e}")

# =============================================
# Processamento de RoomingNights
# =============================================
def process_rooming_nights():
    ini_date = "2024-01-01T00:00:00"
    fin_date = "2026-12-31T23:59:59"
    size = 50
    page = 0

    # Verifica se a pasta de cache existe antes de chamar a API
    if not os.path.exists(CACHE_DIR):
        log_system("Pasta de dados não encontrada. Será executada carga completa (Full Load) para RoomingNights.")
        full_load = True
    else:
        full_load = False
        log_system("Pasta de dados encontrada. Executando carga incremental para RoomingNights.")

    token = get_cached_token()
    headers = {
        "X-API-VERSION": "1",
        "X-API-TENANT-NAME": "thecoralbeachresort",
        "X-API-PROPERTY-CODE": "1",
        "X-API-PARTNER-USERID": "0",
        "X-API-LANGUAGE-CODE": "pt-br",
        "X-Client-Id": "THECORALBR",
        "Authorization": f"Bearer {token}"
    }
    
    # Busca todos os registros da API
    all_data = []
    while True:
        data = fetch_rooming_nights(page, headers, ini_date, fin_date, size)
        if not isinstance(data, list):
            log_error(f"RoomingNights - Página {page} com estrutura inesperada: {data}")
            break
        count = len(data)
        log_system(f"RoomingNights - [Página {page}] {count} registros carregados.")
        if count == 0:
            break
        all_data.extend(data)
        if count < size:
            break
        page += 1
    log_system(f"RoomingNights - Total de registros obtidos da API: {len(all_data)}")
    
    if not os.path.exists(ROOMING_CACHE_FILE):
        log_system("Arquivo de cache de RoomingNights não encontrado. Executando carga completa (Full Load).")
        truncate_rooming_nights()
        upsert_rooming_nights(all_data)
        ensure_dir(CACHE_DIR)
        save_cache_file(all_data, ROOMING_CACHE_FILE, "RoomingNights")
        return

    # Caso exista o cache, executa carga incremental
    try:
        with open(ROOMING_CACHE_FILE, "r", encoding="utf-8") as f:
            local_data = json.load(f)
        log_system(f"RoomingNights - Cache carregado. Registros: {len(local_data)}")
    except Exception as e:
        log_error(f"RoomingNights - Erro ao carregar cache: {e}")
        local_data = []

    comparison = compare_data(all_data, local_data, rooming_key)
    new_records = comparison["new"]
    updates = comparison["updates"]
    log_system(f"RoomingNights - Registros novos: {len(new_records)}; atualizados: {len(updates)}")
    
    
# Para RoomingNights
    # Logs incrementais só se houver algo novo ou alterado
    if not new_records and not updates:
        log_update_data("RoomingNights - Nenhum dado novo/atualizado")
    else:
        for rec in new_records:
            log_update_data(f"[NEW] ID {rec['id']} Cliente: {rec.get('companyName','N/A')}")
        for rec in updates:
            log_update_data(f"[UPDATE] ID {rec['id']} Diferenças: {rec.get('diff','')}")
    
    # Atualiza cache
    save_cache_file(all_data, ROOMING_CACHE_FILE, "RoomingNights")
    
    # Prepara listas limpas (remove campo diff) e dispara inserções/atualizações separadas
    clean_new = [{k:v for k,v in r.items() if k!="diff"} for r in new_records]
    clean_upd = [{k:v for k,v in r.items() if k!="diff"} for r in updates]

    if clean_new:
        log_system(f"RoomingNights - Inserindo {len(clean_new)} novos registros")
        upsert_rooming_nights(clean_new)
    if clean_upd:
        log_system(f"RoomingNights - Atualizando {len(clean_upd)} registros")
        upsert_rooming_nights(clean_upd)
    if not clean_new and not clean_upd:
        log_system("RoomingNights - Nenhum registro para upsert incremental.")


# =============================================
# Processamento de OccupationInventory
# =============================================
def process_occupation_inventory_monthly():
    # Cabeçalhos fixos
    token = get_cached_token()
    headers = {
        "X-API-VERSION": "1",
        "X-API-TENANT-NAME": "thecoralbeachresort",
        "X-API-PROPERTY-CODE": "1",
        "X-API-PARTNER-USERID": "0",
        "X-API-LANGUAGE-CODE": "pt-br",
        "X-Client-Id": "THECORALBR",
        "Authorization": f"Bearer {token}"
    }

    # Período principal
    start_period = datetime(2024, 1, 1)
    end_period   = datetime(2026, 12, 31)
    size = 50

    all_data = []
    current = start_period

    while current <= end_period:
        # Início do mês
        ini_date = current.strftime('%Y-%m-01T00:00:00')
        # Fim do mês
        next_month = (current.replace(day=28) + timedelta(days=4)).replace(day=1)
        last_day = next_month - timedelta(seconds=1)
        fin_date = last_day.strftime('%Y-%m-%dT%H:%M:%S')

        page = 0
        while True:
            data = fetch_occupation_inventory(page, headers, ini_date, fin_date, size)
            if not isinstance(data, list):
                log_error(f"OccupationInventory - Página {page} estrutura inesperada: {data}")
                break
            if not data:
                log_system(f"Nenhum registro para período {ini_date} a {fin_date}")
                break

            all_data.extend(data)
            log_system(f"OccupationInventory - Mês {current.strftime('%Y-%m')} Página {page}: {len(data)} registros")

            if len(data) < size:
                break
            page += 1
            time.sleep(0.2)

        # Avança para próximo mês
        # calcula primeiro dia do próximo mês
        current = next_month

    log_system(f"OccupationInventory - Total registros obtidos: {len(all_data)}")

    # 2) Se não há cache (arquivo), faz Full Load e retorna
    if not os.path.exists(OCCUPATION_CACHE_FILE):
        log_system("Cache não encontrado. Executando Full Load para OccupationInventory.")
        truncate_occupation_inventory()
        upsert_occupation_inventory(all_data)
        ensure_dir(CACHE_DIR)
        save_cache_file(all_data, OCCUPATION_CACHE_FILE, "OccupationInventory")
        return

    # 3) Cache existe → carrega e compara para incremental
    try:
        with open(OCCUPATION_CACHE_FILE, "r", encoding="utf-8") as f:
            local_data = json.load(f)
        log_system(f"OccupationInventory - Cache carregado. Registros: {len(local_data)}")
    except Exception as e:
        log_error(f"OccupationInventory - Erro ao carregar cache: {e}")
        local_data = []

    comparison = compare_data(all_data, local_data, occupation_key)
    new_records = comparison["new"]
    updates     = comparison["updates"]
    log_system(f"OccupationInventory - Registros novos: {len(new_records)}; atualizados: {len(updates)}")

    # 4) Logs apenas se houver algo novo ou alterado
    if not new_records and not updates:
        log_update_data("OccupationInventory - Nenhum dado novo/atualizado")
    else:
        for rec in new_records:
            log_update_data(f"[NEW] Key {occupation_key(rec)}")
        for rec in updates:
            log_update_data(f"[UPDATE] Key {occupation_key(rec)} Diferenças: {rec.get('diff','')}")

    # 5) Atualiza cache antes de dar upsert
    save_cache_file(all_data, OCCUPATION_CACHE_FILE, "OccupationInventory")

    # 6) Prepara duas listas sem o campo 'diff'
    clean_new = [{k: v for k, v in rec.items() if k != "diff"} for rec in new_records]
    clean_upd = [{k: v for k, v in rec.items() if k != "diff"} for rec in updates]

    # 7) Dispara inserções e atualizações separadas
    if clean_new:
        log_system(f"OccupationInventory - Inserindo {len(clean_new)} novos registros")
        upsert_occupation_inventory(clean_new)
    if clean_upd:
        log_system(f"OccupationInventory - Atualizando {len(clean_upd)} registros")
        upsert_occupation_inventory(clean_upd)
    if not clean_new and not clean_upd:
        log_system("OccupationInventory - Nenhum registro para upsert incremental.")


# =============================================
# Main
# =============================================
def main():
    process_rooming_nights()
    process_occupation_inventory_monthly()

if __name__ == '__main__':
    main()
