import requests
import pymysql
import re
import datetime
import unicodedata
import time
import json

# ==========================================
# CONFIGURAÇÕES DE AMBIENTE
# ==========================================
MOODLE_URL = "https://dev.fatipi.edu.br/webservice/rest/server.php"
TOKEN = "20da6b150d97710d06c6c1697304337e"
CATEGORIA_MODELO_ID = 124

DB_HOST = "db_moodle_test"
DB_USER = "root"
DB_PASS = "AxBvopFmOPf6gWd576VA"
DB_NAME = "moodle_dev"
PREFIX = "mdldp_" 

OFFSET_HORARIO = 3 # UTC-3 São Paulo

class Cores:
    VERDE = '\033[92m'
    AMARELO = '\033[93m'
    VERMELHO = '\033[91m'
    AZUL = '\033[94m'
    BOLD = '\033[1m'
    FIM = '\033[0m'

# ==========================================
# MOTOR DE TRATAMENTO E DATAS
# ==========================================

def super_limpeza(texto):
    if not texto: return ""
    t = texto.split('$#')[0].strip()
    t = re.sub(r'-(MODELO|202\d\.\d(_MODELO)?)$', '', t, flags=re.IGNORECASE)
    t = ''.join(c for c in unicodedata.normalize('NFD', t) if unicodedata.category(c) != 'Mn')
    return re.sub(r'[^a-zA-Z0-9]', '', t).lower().strip()

def extrair_ordem(fullname, shortname):
    match = re.search(r'\$#DISC10101(\d{2})#\$', f"{fullname} {shortname}")
    return int(match.group(1)) if match else None

def to_moodle_ts(dt):
    return int(dt.timestamp()) + (OFFSET_HORARIO * 3600)

def call_api(func, params_extra=None):
    p = {"wstoken": TOKEN, "moodlewsrestformat": "json", "wsfunction": func}
    if params_extra: p.update(params_extra)
    return requests.post(MOODLE_URL, data=p).json()

# O Novo Motor Matemático de Tags
def calcular_janela_tag(nome, inicio_disciplina):
    # Aceita APENAS as tags autorizadas (Adicionado MODULO na lista VIP)
    match = re.search(r'\$#(FORUM|CADERNO|TAREFA|RECUP|REC|MODULO)(\d+)?#\$', nome, re.IGNORECASE)
    if not match: 
        return None, None
        
    palavra = match.group(1).upper()
    num_str = match.group(2)
    data_base = inicio_disciplina.replace(hour=0, minute=0, second=0, microsecond=0)
    
    if palavra in ['REC', 'RECUP']:
        # Abertura cravada: 49 dias após o inicio do curso
        abertura = data_base + datetime.timedelta(days=49)
        semanas = int(num_str) if num_str else 2 
        fechamento = abertura + datetime.timedelta(days=(semanas * 7) - 1, hours=23, minutes=59, seconds=59)
        return abertura, fechamento
    else:
        # Padrão: FORUM, CADERNO, TAREFA, MODULO
        idx = int(num_str) if num_str else 1
        abertura = data_base + datetime.timedelta(days=(idx - 1) * 7)
        fechamento = abertura + datetime.timedelta(days=6, hours=23, minutes=59, seconds=59)
        return abertura, fechamento

# ==========================================
# EXECUÇÃO PRINCIPAL
# ==========================================

def executar():
    print(f"{Cores.AZUL}{'='*80}{Cores.FIM}")
    print(f"{Cores.BOLD}FATIPI SISTEMAS - GERENCIADOR DE MATRIZES v28.0 (THE MISSING LINK){Cores.FIM}")
    print(f"{Cores.AZUL}{'='*80}{Cores.FIM}")

    cat_id = input(f"{Cores.BOLD}1. ID Categoria Destino (TOTVS):{Cores.FIM} ")
    data_str = input(f"{Cores.BOLD}2. Data Início da 1ª Disciplina (DD/MM/AAAA):{Cores.FIM} ")
    try:
        DT_BASE = datetime.datetime.strptime(data_str, "%d/%m/%Y")
    except:
        print(f"{Cores.VERMELHO}Data inválida!{Cores.FIM}"); return

    print(f"\n{Cores.AMARELO}Buscando correspondências...{Cores.FIM}")
    res_m = call_api("core_course_get_courses_by_field", {"field": "category", "value": CATEGORIA_MODELO_ID})
    res_b = call_api("core_course_get_courses_by_field", {"field": "category", "value": cat_id})

    modelos = {super_limpeza(m['shortname']): m for m in res_m.get('courses', []) if extrair_ordem(m['fullname'], m['shortname'])}
    
    plano_inicial = []
    for b in res_b.get('courses', []):
        h = super_limpeza(b['shortname'])
        if h in modelos:
            m = modelos[h]
            ordem = extrair_ordem(m['fullname'], m['shortname'])
            offset = 6 if h.startswith('31') else 0
            inicio_disc = DT_BASE + datetime.timedelta(days=((ordem - 1) * 35) + offset)
            plano_inicial.append({"base": b, "modelo": m, "inicio": inicio_disc})

    if not plano_inicial:
        print(f"{Cores.VERMELHO}Nenhuma disciplina encontrada.{Cores.FIM}"); return

    print(f"\n{Cores.VERDE}{Cores.BOLD}LISTA DE DISCIPLINAS ENCONTRADAS:{Cores.FIM}")
    for i, p in enumerate(plano_inicial):
        print(f"{i:<3} | {p['base']['shortname'][:50]:<50} | {p['inicio'].strftime('%d/%m/%Y')}")

    remocao = input(f"\n{Cores.AMARELO}Remover alguma? (IDs separados por vírgula ou ENTER para processar todas):{Cores.FIM} ")
    indices_remover = [int(x.strip()) for x in remocao.split(',')] if remocao.strip() else []
    plano_final = [p for i, p in enumerate(plano_inicial) if i not in indices_remover]

    if not plano_final: return

    try:
        db = pymysql.connect(host=DB_HOST, user=DB_USER, password=DB_PASS, database=DB_NAME, autocommit=True)
        cursor = db.cursor(pymysql.cursors.DictCursor)
    except Exception as e:
        print(f"{Cores.VERMELHO}Erro DB: {e}{Cores.FIM}"); return

    if input(f"\n{Cores.BOLD}CONFIRMA EXECUÇÃO? (S/N):{Cores.FIM} ").upper() != 'S': db.close(); return

    # =============================================================
    # O CÁLCULO DINÂMICO DA DATA GLOBAL DE FECHAMENTO
    # =============================================================
    print(f"\n{Cores.AMARELO}* Analisando matrizes para calcular a Data de Fechamento Global...{Cores.FIM}")
    data_fechamento_global = None

    for p in plano_final:
        m_id = p['modelo']['id']
        inicio = p['inicio']

        cursor.execute(f"SELECT cm.instance, m.name as modtype FROM {PREFIX}course_modules cm JOIN {PREFIX}modules m ON cm.module = m.id WHERE cm.course = %s", (m_id,))
        for row in cursor.fetchall():
            mod = row['modtype'].lower()
            tab = {"forum": "forum", "quiz": "quiz", "assign": "assign"}.get(mod)
            if not tab: continue

            cursor.execute(f"SELECT name FROM {PREFIX}{tab} WHERE id = %s", (row['instance'],))
            res = cursor.fetchone()
            if res and res['name']:
                _, dt_f = calcular_janela_tag(res['name'], inicio)
                if dt_f:
                    if not data_fechamento_global or dt_f > data_fechamento_global:
                        data_fechamento_global = dt_f

    if not data_fechamento_global:
        ultima_data_inicio = max(p['inicio'] for p in plano_final)
        _, data_fechamento_global = calcular_janela_tag("$#TAREFA05#$", ultima_data_inicio)

    ts_fechamento_global = to_moodle_ts(data_fechamento_global)
    print(f"  > Fim do semestre cravado na última atividade do bloco: {Cores.BOLD}{data_fechamento_global.strftime('%d/%m/%Y %H:%M:%S')}{Cores.FIM}")

    for p in plano_final:
        c_id = p['base']['id']
        m_id = p['modelo']['id']
        ts_inicio_disc = to_moodle_ts(p['inicio'])
        
        print(f"\n{Cores.AZUL}>>> PROCESSANDO {p['base']['shortname']}{Cores.FIM}")

        # 1. GROUND ZERO
        print(f"    {Cores.AMARELO}* Limpando terreno (Ground Zero)...{Cores.FIM}")
        cursor.execute(f"SELECT id FROM {PREFIX}grade_categories WHERE courseid = %s AND parent IS NULL LIMIT 1", (c_id,))
        root_cat = cursor.fetchone()
        
        if root_cat:
            root_id = root_cat['id']
            cursor.execute(f"UPDATE {PREFIX}grade_items SET categoryid = %s WHERE courseid = %s AND itemtype != 'course' AND itemtype != 'category'", (root_id, c_id))
            cursor.execute(f"DELETE FROM {PREFIX}grade_items WHERE courseid = %s AND itemtype = 'category'", (c_id,))
            cursor.execute(f"DELETE FROM {PREFIX}grade_categories WHERE courseid = %s AND parent IS NOT NULL", (c_id,))

        cursor.execute(f"SELECT cm.id as cmid, cm.instance, m.name as modname FROM {PREFIX}course_modules cm JOIN {PREFIX}modules m ON cm.module = m.id WHERE cm.course = %s", (c_id,))
        for row in cursor.fetchall():
            mod_name, inst_id, cm_id = row['modname'], row['instance'], row['cmid']
            cursor.execute(f"DELETE FROM {PREFIX}grade_items WHERE courseid = %s AND itemtype = 'mod' AND itemmodule = %s AND iteminstance = %s", (c_id, mod_name, inst_id))
            try: cursor.execute(f"DELETE FROM {PREFIX}{mod_name} WHERE id = %s", (inst_id,))
            except: pass
            cursor.execute(f"DELETE FROM {PREFIX}course_modules WHERE id = %s", (cm_id,))

        cursor.execute(f"DELETE FROM {PREFIX}course_sections WHERE course = %s AND section > 0", (c_id,))
        cursor.execute(f"UPDATE {PREFIX}course_sections SET name = NULL, summary = '', sequence = '' WHERE course = %s AND section = 0", (c_id,))

        # 2. CONFIGURAÇÃO GERAL E IMPORTAÇÃO
        print(f"    * Alterando layout e clonando config...")
        call_api("core_course_update_courses", {"courses[0][id]": c_id, "courses[0][format]": "onetopic", "courses[0][visible]": 1})
        time.sleep(1)

        cursor.execute(f"DELETE FROM {PREFIX}course_format_options WHERE courseid = %s AND format = 'onetopic'", (c_id,))
        cursor.execute(f"SELECT name, value FROM {PREFIX}course_format_options WHERE courseid = %s AND format = 'onetopic' AND sectionid = 0", (m_id,))
        for opt in cursor.fetchall():
            cursor.execute(f"INSERT INTO {PREFIX}course_format_options (courseid, format, sectionid, name, value) VALUES (%s, 'onetopic', 0, %s, %s)", (c_id, opt['name'], opt['value']))
        
        call_api("core_course_import_course", {"importfrom": m_id, "importto": c_id, "options[0][name]": "activities", "options[0][value]": 1})
        time.sleep(4)

        # 3. MAPEAMENTO REVERSO DE ABAS
        print(f"    * Reconstruindo relacionamentos visuais das abas...")
        cursor.execute(f"SELECT name FROM {PREFIX}course_sections WHERE course = %s AND section = 0", (m_id,))
        nome_sec_0 = cursor.fetchone()
        if nome_sec_0 and nome_sec_0['name']:
            cursor.execute(f"UPDATE {PREFIX}course_sections SET name = %s WHERE course = %s AND section = 0", (nome_sec_0['name'], c_id))

        cursor.execute(f"SELECT id, section FROM {PREFIX}course_sections WHERE course = %s AND section > 0", (m_id,))
        for row in cursor.fetchall():
            cursor.execute(f"SELECT id FROM {PREFIX}course_sections WHERE course = %s AND section = %s", (c_id, row['section']))
            dest_row = cursor.fetchone()
            if dest_row:
                new_sec_id = dest_row['id']
                cursor.execute(f"DELETE FROM {PREFIX}course_format_options WHERE courseid = %s AND format = 'onetopic' AND sectionid = %s", (c_id, new_sec_id))
                cursor.execute(f"SELECT name, value FROM {PREFIX}course_format_options WHERE courseid = %s AND format = 'onetopic' AND sectionid = %s", (m_id, row['id']))
                for opt in cursor.fetchall():
                    cursor.execute(f"INSERT INTO {PREFIX}course_format_options (courseid, format, sectionid, name, value) VALUES (%s, 'onetopic', %s, %s, %s)", (c_id, new_sec_id, opt['name'], opt['value']))

        # 4. APLICAÇÃO DE DATAS E SINCRONIZAÇÃO DE CALENDÁRIO
        print(f"    * Lendo Tags Estritas, configurando Prazos e sincronizando Calendário...")
        cursor.execute(f"SELECT cm.id as cmid, cm.instance, m.name as modtype FROM {PREFIX}course_modules cm JOIN {PREFIX}modules m ON cm.module = m.id WHERE cm.course = %s", (c_id,))
        
        for item in cursor.fetchall():
            mod = item['modtype'].lower()
            tab = {"forum": "forum", "quiz": "quiz", "assign": "assign"}.get(mod)
            if not tab: continue

            cursor.execute(f"SELECT id, name, intro FROM {PREFIX}{tab} WHERE id = %s", (item['instance'],))
            res = cursor.fetchone()
            if res and res['name']:
                dt_a, dt_f = calcular_janela_tag(res['name'], p['inicio'])
                
                if dt_a and dt_f:
                    # Incluído o MODULO na exclusão da tag para as atividades
                    tag_match = re.search(r'(\$#(FORUM|CADERNO|TAREFA|RECUP|REC|MODULO)(\d+)?#\$)', res['name'], re.IGNORECASE)
                    tag_completa = tag_match.group(1)
                    nome_limpo = res['name'].replace(tag_completa, '').strip()
                    
                    ts_a, ts_f = to_moodle_ts(dt_a), to_moodle_ts(dt_f)
                    novo_intro = (res['intro'] or "") + f"\n"
                    
                    avail = json.dumps({"op":"&","c":[{"type":"date","d":">=","t":ts_a}],"showc":[True]})
                    cursor.execute(f"UPDATE {PREFIX}course_modules SET availability=%s WHERE id=%s", (avail, item['cmid']))
                    
                    if tab == "forum":
                        cursor.execute(f"UPDATE {PREFIX}forum SET name=%s, intro=%s, duedate=%s, cutoffdate=%s WHERE id=%s", (nome_limpo, novo_intro, ts_f, ts_f, item['instance']))
                    elif tab == "quiz":
                        cursor.execute(f"UPDATE {PREFIX}quiz SET name=%s, intro=%s, timeopen=%s, timeclose=%s WHERE id=%s", (nome_limpo, novo_intro, ts_a, ts_f, item['instance']))
                    elif tab == "assign":
                        cursor.execute(f"UPDATE {PREFIX}assign SET name=%s, intro=%s, allowsubmissionsfromdate=%s, duedate=%s, cutoffdate=%s WHERE id=%s", (nome_limpo, novo_intro, ts_a, ts_f, ts_f, item['instance']))

                    cursor.execute(f"UPDATE {PREFIX}event SET timestart=%s WHERE modulename=%s AND instance=%s AND eventtype IN ('open', 'opensubmissions')", (ts_a, mod, item['instance']))
                    cursor.execute(f"UPDATE {PREFIX}event SET timestart=%s WHERE modulename=%s AND instance=%s AND eventtype IN ('due', 'close', 'cutoff')", (ts_f, mod, item['instance']))

        cursor.execute(f"SELECT id, fullname FROM {PREFIX}grade_categories WHERE courseid = %s AND fullname LIKE '%%$#%%'", (c_id,))
        for cat in cursor.fetchall():
            n_limpo = re.sub(r'\$#.*?#\$', '', cat['fullname']).strip()
            cursor.execute(f"UPDATE {PREFIX}grade_categories SET fullname = %s WHERE id = %s", (n_limpo, cat['id']))

        cursor.execute(f"SELECT id, itemname FROM {PREFIX}grade_items WHERE courseid = %s AND itemtype = 'mod' AND itemname LIKE '%%$#%%'", (c_id,))
        for gi in cursor.fetchall():
            n_limpo = re.sub(r'\$#.*?#\$', '', gi['itemname']).strip()
            cursor.execute(f"UPDATE {PREFIX}grade_items SET itemname = %s WHERE id = %s", (n_limpo, gi['id']))

        # =============================================================
        # ATUALIZADO: Lendo e configurando o MODULO e RECUP nas Seções
        # =============================================================
        cursor.execute(f"SELECT id, name, summary, availability FROM {PREFIX}course_sections WHERE course = %s", (c_id,))
        for sec in cursor.fetchall():
            nome_original = sec['name'] or ""
            cursor.execute(f"UPDATE {PREFIX}course_sections SET visible=1 WHERE id=%s", (sec['id'],))
            
            dt_a, _ = calcular_janela_tag(nome_original, p['inicio'])
            if dt_a:
                tag_match = re.search(r'(\$#(FORUM|CADERNO|TAREFA|RECUP|REC|MODULO)(\d+)?#\$)', nome_original, re.IGNORECASE)
                tag_completa = tag_match.group(1)
                n_sec = nome_original.replace(tag_completa, '').strip()
                
                avail = json.dumps({"op":"&","c":[{"type":"date","d":">=","t":to_moodle_ts(dt_a)}],"showc":[True]})
                novo_summary = (sec['summary'] or "") + f"\n"
                cursor.execute(f"UPDATE {PREFIX}course_sections SET name=%s, availability=%s, summary=%s WHERE id=%s", (n_sec, avail, novo_summary, sec['id']))

        # 5. A MARRETADA SQL GLOBAL
        ts_now = int(time.time())
        cursor.execute(f"UPDATE {PREFIX}course SET startdate = %s, enddate = %s, timemodified = %s, cacherev = %s WHERE id = %s", (ts_inicio_disc, ts_fechamento_global, ts_now, ts_now, c_id))
        cursor.execute(f"UPDATE {PREFIX}grade_items SET needsupdate = 1 WHERE courseid = %s", (c_id,))
        
        print(f"    {Cores.VERDE}* Matriz blindada, Datas globais injetadas e Abas limpas!{Cores.FIM}")

    db.close()
    print(f"\n{Cores.VERDE}{Cores.BOLD}OPERAÇÃO FINALIZADA.{Cores.FIM}")

if __name__ == "__main__": executar()
