"""Service de vérification des messages ENT77 et création automatique d'interventions.

- Authentification via API ENT77
- Récupération des nouveaux messages (paginée)
- Détection des messages transférés
- Création d'interventions GMAO automatiques
- Préservation de l'état lu/non-lu
"""
import re
import logging
from datetime import datetime, timezone

import requests
from flask import current_app
from app_new.extensions import db
from app_new.lib_ext.ent_crypto import decrypt_value
from app_new.ent.models import EntCredential, EntMessage, EntConfig
from app_new.core.models import Intervention, InterventionComment, StatusChange, CollegeClosure, Room

logger = logging.getLogger(__name__)

BASE_URL = 'https://ent77.seine-et-marne.fr'
LOGIN_URL = f'{BASE_URL}/auth/login'
LIST_URL = f'{BASE_URL}/conversation/list/inbox'
DETAIL_URL = f'{BASE_URL}/conversation/api/messages/'
TOGGLE_UNREAD_URL = f'{BASE_URL}/conversation/toggleUnread'
MOVE_URL = f'{BASE_URL}/conversation/api/messages/'  # Ajouté pour déplacer les messages
FOLDERS_URL = f'{BASE_URL}/conversation/api/folders'  # Ajouté pour lister les dossiers

# Mots-clés pour détecter une demande d'intervention
INTERVENTION_KEYWORDS = [
    'panne', 'cassé', 'cassée', 'casser', 'brisé', 'brisée',
    'dysfonctionn', 'défectueux', 'défectueuse', 'en panne',
    'hors service', 'fuit', 'fuite', 'bloqué', 'bloquée',
    'ne fonctionne pas', 'ne marche plus', 'ne s\'allume',
    'urgen', 'signal', 'problème', 'dégradé',
    'à remplacer', 'remplacement', 'réparation', 'réparer',
    'fenêtre fendue', 'vitre cassée', 'porte bloquée',
    'fuite d\'eau', 'pas d\'eau', 'pas d\'électricité', 'coupure',
    'infiltration', 'humidité', 'moisissure', 'chasse d\'eau',
]


def is_intervention_request(text: str) -> bool:
    """Détecte si le texte contient des mots-clés de demande d'intervention."""
    text_lower = text.lower()
    return any(kw in text_lower for kw in INTERVENTION_KEYWORDS)


def extract_sender(msg_json: dict) -> tuple:
    """Extrait l'expéditeur réel d'un message (direct ou transféré).
    
    Retourne (sender_name, is_forwarded, original_sender).
    - Message direct: (from.displayName, False, '')
    - Message transféré: (from.displayName, True, 'NOM ORIGINAL')
    - Si parsing échoue sur transfert: fallback sur from.displayName
    """
    subject = msg_json.get('subject', '')
    body = msg_json.get('body', '')
    from_obj = msg_json.get('from', {})
    sender_display = from_obj.get('displayName', 'Inconnu') if isinstance(from_obj, dict) else 'Inconnu'
    
    is_forwarded = subject.startswith('Tr :') or 'Message transféré' in body
    
    if is_forwarded:
        # Parser le bloc "De : XXX" dans le body (format standard ENT77)
        body_text = re.sub('<[^<]+?>', '', body)
        # Rechercher "De : Nom <email>" ou "De : Nom" — ne capturer que le nom
        # Arrêter avant les métadonnées suivantes (Date :, Objet :, À :, Envoyé :)
        match = re.search(r'De\s*:\s*(.+?)(?:\s*<.+?>|\s*(?:Date\s*:|Objet\s*:|À\s*:|Envoyé\s*:)|$)', body_text, re.MULTILINE)
        if match:
            original_sender = match.group(1).strip()
            # Sécurité : limiter à 60 caractères
            if len(original_sender) > 60:
                original_sender = original_sender[:60].rsplit(' ', 1)[0] + '…'
            return sender_display, True, original_sender
        # Fallback : si "De :" introuvable, on garde le transféreur
        return sender_display, True, sender_display
    
    return sender_display, False, ''


def login_ent(session, username: str, password: str) -> dict:
    """Authentification ENT77, retourne les headers avec XSRF token."""
    try:
        login_resp = session.get(LOGIN_URL, timeout=10)
        logger.info(f"Login page status: {login_resp.status_code}")
    except Exception as e:
        logger.error(f"Failed to reach login page: {e}")
        return None

    headers = {
        'User-Agent': 'Mozilla/5.0 (GMAO-College)',
        'X-Requested-With': 'XMLHttpRequest',
        'Referer': LOGIN_URL,
        'Accept': 'application/json',
    }
    resp = session.post(LOGIN_URL, data={'email': username, 'password': password},
                        headers=headers, allow_redirects=True, timeout=10)

    logger.info(f"Login response status: {resp.status_code}")
    cookies = session.cookies.get_dict()
    logger.info(f"Cookies after login: {list(cookies.keys())}")
    
    if not any(k in cookies for k in ('oneSessionId', 'XSRF-TOKEN')) \
            and cookies.get('authenticated') != 'true':
        logger.error(f"Login failed. Cookies: {list(cookies.keys())}")
        logger.error(f"Response text (first 500 chars): {resp.text[:500]}")
        return None

    xsrf = cookies.get('XSRF-TOKEN', '')
    headers['X-XSRF-TOKEN'] = xsrf
    logger.info(f"ENT login OK. XSRF: {'present' if xsrf else 'MISSING'}")
    return headers


def fetch_new_messages(session, headers, last_msg_id: str = None, max_messages: int = 20):
    """Récupère les nouveaux messages depuis last_msg_id."""
    all_messages = []
    page = 0
    while True:
        url = f'{LIST_URL}?page={page}'
        try:
            resp = session.get(url, headers=headers, timeout=10)
        except Exception as e:
            logger.error(f"Error fetching page {page}: {e}")
            break
        if resp.status_code != 200:
            break
        try:
            messages = resp.json()
        except Exception:
            break
        if not isinstance(messages, list) or len(messages) == 0:
            break
        
        seen_ids = {m['id'] for m in all_messages}
        new_count = 0
        for m in messages:
            if m.get('id') not in seen_ids:
                all_messages.append(m)
                seen_ids.add(m.get('id'))
                new_count += 1
        
        if new_count == 0 or len(messages) < 25:
            break
        page += 1
    
    # Filtrer les nouveaux messages
    new_messages = []
    for msg in all_messages:
        mid = str(msg.get('id'))
        if mid == last_msg_id:
            break
        new_messages.append(msg)
        if len(new_messages) >= max_messages:
            break
    
    return new_messages


def check_ent_messages(app=None):
    """Vérifie les nouveaux messages ENT77 pour tous les comptes actifs.
    
    Called by APScheduler or manually.
    Crée des interventions automatiquement si config.auto_create_intervention.
    """
    # On doit travailler dans un app context
    if app is None:
        from app_new import create_app
        app = create_app()
    
    with app.app_context():
        config = EntConfig.query.first()
        if not config or not config.is_enabled:
            logger.info("ENT checking disabled.")
            return 0
        
        if not config.is_working_time():
            logger.info("Outside working hours, skipping ENT check.")
            return 0
        
        credentials = EntCredential.query.filter_by(is_active=True).all()
        if not credentials:
            logger.info("No active ENT credentials.")
            return 0
        
        total_created = 0
        for cred in credentials:
            try:
                created = _process_credential(cred, config, app)
                total_created += created
            except Exception as e:
                logger.error(f"Error processing credential {cred.id}: {e}")
        
        logger.info(f"ENT check done. {total_created} intervention(s) created.")
        return total_created


def _process_credential(cred, config, app):
    """Traite un compte ENT : vérifie les messages et crée les interventions."""
    # Déchiffrer les identifiants
    try:
        username = decrypt_value(cred.ent_username_encrypted, app)
        password = decrypt_value(cred.ent_password_encrypted, app)
    except Exception as e:
        logger.error(f"Failed to decrypt credentials for user {cred.user_id}: {str(e)}")
        return 0
    
    sess = requests.Session()
    headers = login_ent(sess, username, password)
    if not headers:
        logger.error(f"Login failed for credential {cred.id}")
        return 0
    
    # Récupérer les nouveaux messages
    new_messages = fetch_new_messages(sess, headers, cred.last_msg_id)
    if not new_messages:
        logger.info(f"No new messages for credential {cred.id}.")
        cred.last_check = datetime.now(timezone.utc)
        db.session.commit()
        return 0
    
    created_count = 0
    unread_to_restore = []
    
    for msg in new_messages:
        msg_id = str(msg.get('id'))
        was_unread = msg.get('unread') is True or msg.get('unread') == 1
        
        # Récupérer le détail
        try:
            detail = sess.get(DETAIL_URL + msg_id, headers=headers, timeout=10)
            if detail.status_code != 200:
                logger.warning(f"Detail fetch {msg_id}: {detail.status_code}")
                continue
            detail_json = detail.json()
        except Exception as e:
            logger.error(f"Error fetching detail {msg_id}: {e}")
            continue
        
        body_html = detail_json.get('body', '')
        body_text = re.sub('<[^<]+?>', '', body_html)
        sender_display, is_forwarded, original_sender = extract_sender(detail_json)
        subject = detail_json.get('subject', 'N/A')
        ts = detail_json.get('date')
        msg_date = datetime.fromtimestamp(ts / 1000) if ts else datetime.now(timezone.utc)
        
        # Vérifier si message déjà traité
        existing = EntMessage.query.get(msg_id)
        if existing:
            continue
        
        # Sauvegarder le message en cache
        ent_msg = EntMessage(
            id=msg_id,
            credential_id=cred.id,
            subject=subject,
            sender_name=sender_display,
            sender_id=detail_json.get('from', {}).get('id', '') if isinstance(detail_json.get('from'), dict) else '',
            date=msg_date,
            body=body_html,
            is_forwarded=is_forwarded,
            original_sender=original_sender,
            is_unread=was_unread,
            has_attachment=bool(detail_json.get('attachments')),
        )
        db.session.add(ent_msg)
        
        # Déterminer le demandeur
        requester = original_sender if original_sender else sender_display
        
        # Créer intervention si auto_create activé
        if config.auto_create_intervention and config.default_equipment_id:
            # Vérifier anti-doublon
            existing_interv = Intervention.query.filter_by(ent_message_id=msg_id).first()
            if not existing_interv:
                # Déterminer la priorité à partir du sujet/corps
                priority = 'normale'
                text_lower = (subject + ' ' + body_text).lower()
                if 'urgent' in text_lower:
                    priority = 'urgente'
                elif any(kw in text_lower for kw in ['problème', 'panne', 'fuite', 'cassé']):
                    priority = 'haute'
                
                # Vérifier si demande d'intervention
                is_request = is_intervention_request(subject + ' ' + body_text)
                detection_label = '🔒 Demande d\'intervention détectée' if is_request else 'ℹ️ Information sans action requise'
                
                # Retirer le préfixe "Tr : " du titre
                clean_subject = subject
                if clean_subject.startswith('Tr : '):
                    clean_subject = clean_subject[5:]
                
                # Créer l'intervention
                interv = Intervention(
                    title=f"[ENT] {clean_subject}",
                    description=body_text[:500],
                    type='curatif',
                    priority=priority,
                    status='brouillon',
                    equipment_id=config.default_equipment_id,
                    requester_name=requester,
                    ent_message_id=msg_id,
                    ent_sender=requester,
                    requested_at=msg_date,
                )
                db.session.add(interv)
                db.session.flush()  # obtenir interv.id
                
                # Créer un commentaire avec la détection + corps du message
                comment_content = f"{detection_label}\n\n{body_text}"
                comment = InterventionComment(
                    intervention_id=interv.id,
                    author_id=cred.user_id,
                    content=comment_content,
                )
                db.session.add(comment)
                
                # Enregistrer le changement de statut initial
                change = StatusChange(
                    intervention_id=interv.id,
                    from_status=None,
                    to_status='brouillon',
                    changed_by_id=cred.user_id,
                    comment=f'Création automatique depuis message ENT (ID: {msg_id})',
                )
                db.session.add(change)
                
                # Lier le message à l'intervention
                ent_msg.intervention_id = interv.id
                
                created_count += 1
                logger.info(f"Created intervention #{interv.id} from ENT message {msg_id}")
        
        # Marquer pour restauration unread
        if was_unread:
            unread_to_restore.append(msg_id)
    
    # Sauvegarder le dernier message traité
    if new_messages:
        cred.last_msg_id = str(new_messages[0].get('id'))
    cred.last_check = datetime.now(timezone.utc)
    db.session.commit()
    
    # Restaurer l'état non-lu
    if unread_to_restore:
        try:
            sess.post(TOGGLE_UNREAD_URL,
                      json={'id': unread_to_restore, 'unread': True},
                      headers=headers, timeout=10)
        except Exception as e:
            logger.error(f"Error restoring unread state: {e}")
    
    return created_count


def get_or_create_processed_folder(session, headers):
    """Récupère l'ID du dossier 'Traités' depuis la configuration."""
    from app_new.outlook.processed_folder_config import ProcessedFolderConfig
    import logging
    logger = logging.getLogger(__name__)
    
    config = ProcessedFolderConfig.get_config()
    
    # Utiliser l'ID configuré
    if config.ent_folder_id:
        logger.info(f"get_or_create_processed_folder ENT: Using configured folder ID: {config.ent_folder_id}")
        return config.ent_folder_id
    
    # Fallback: chercher dans la liste des dossiers
    try:
        resp = session.get(FOLDERS_URL, headers=headers, timeout=10)
        logger.info(f"get_or_create_processed_folder ENT: GET folders -> {resp.status_code}")
        
        if resp.status_code == 200:
            folders = resp.json()
            logger.info(f"get_or_create_processed_folder ENT: {len(folders) if isinstance(folders, list) else 'dict'} folders")
            
            if isinstance(folders, list):
                for folder in folders:
                    if isinstance(folder, dict):
                        name = folder.get('name', '')
                        folder_id = folder.get('id')
                        if name.lower() in ['traités', 'traites', 'processed']:
                            logger.info(f"get_or_create_processed_folder ENT: Found '{name}' -> {folder_id}")
                            return folder_id
        
        # Créer le dossier "Traités"
        logger.info("get_or_create_processed_folder ENT: Creating folder")
        create_resp = session.post(FOLDERS_URL, headers=headers, json={'name': 'Traités'}, timeout=10)
        logger.info(f"get_or_create_processed_folder ENT: POST create -> {create_resp.status_code}")
        
        if create_resp.status_code in [200, 201]:
            folder_id = create_resp.json().get('id')
            logger.info(f"get_or_create_processed_folder ENT: Created -> {folder_id}")
            return folder_id
    except Exception as e:
        logger.error(f"Erreur récupération/création dossier Traités ENT: {e}")
    
    return None


def move_ent_message_to_processed(message_id, session=None, headers=None):
    """Déplace un message ENT vers le dossier 'Traités'.
    
    Args:
        message_id: ID du message ENT
        session: Session requests (optionnel, sera créée si besoin)
        headers: Headers avec auth (optionnel)
    
    Returns:
        dict: {'success': bool, 'error': str (si erreur)}
    """
    from app_new.ent.models import EntCredential
    from app_new.lib_ext.ent_crypto import decrypt_value
    from flask import current_app
    import logging
    
    logger = logging.getLogger(__name__)
    
    # Si pas de session fournie, en créer une
    if session is None:
        cred = EntCredential.query.filter_by(is_active=True).first()
        if not cred:
            logger.error("move_ent_message_to_processed: Aucun credential actif")
            return {'success': False, 'error': 'Aucun credential actif'}
        
        session = requests.Session()
        headers = login_ent(session, decrypt_value(cred.ent_username_encrypted, current_app._get_current_object()), 
                            decrypt_value(cred.ent_password_encrypted, current_app._get_current_object()))
        if not headers:
            logger.error("move_ent_message_to_processed: Échec de connexion ENT")
            return {'success': False, 'error': 'Échec de connexion ENT'}
    
    # Récupérer ou créer le dossier Traités
    folder_id = get_or_create_processed_folder(session, headers)
    if not folder_id:
        logger.error("move_ent_message_to_processed: Impossible de trouver/créer le dossier Traités")
        return {'success': False, 'error': 'Impossible de trouver/créer le dossier Traités'}
    
    logger.info(f"move_ent_message_to_processed: Dossier Traités ID: {folder_id}")
    
    # Déplacer le message - API correcte selon HAR
    # PUT /conversation/move/userfolder/{folder_id} avec body {"id": ["message_id"]}
    try:
        move_url = f"https://ent77.seine-et-marne.fr/conversation/move/userfolder/{folder_id}"
        logger.info(f"move_ent_message_to_processed: Appel API {move_url}")
        
        resp = session.put(move_url, headers=headers, json={"id": [message_id]}, timeout=10)
        logger.info(f"move_ent_message_to_processed: Réponse {resp.status_code}: {resp.text[:200]}")
        
        if resp.status_code in [200, 201]:
            return {'success': True}
        else:
            return {'success': False, 'error': f'API erreur {resp.status_code}: {resp.text[:100]}'}
    except Exception as e:
        logger.error(f"move_ent_message_to_processed: Exception {e}")
        return {'success': False, 'error': str(e)}