#!/usr/bin/env python3
"""
CRon — Générateur de compte rendu de réunion — CLI
Stack : Whisper API (transcription) + Mistral API (génération)
"""

import os
import sys
import json
import tempfile
import threading
import time
import wave
import argparse
from datetime import datetime
from pathlib import Path

# ─────────────────────────────────────────────
# CONFIG
# ─────────────────────────────────────────────
CONFIG_FILE = Path.home() / ".config" / "cron-cr" / "config.json"
DEFAULT_CONFIG = {
    "openai_api_key": "",
    "mistral_api_key": "",
    "mistral_model": "mistral-large-latest",
    "output_file": str(Path.home() / "compte_rendus.md"),
    "whisper_mode": "api",
    "samplerate": 44100,
}

# ─────────────────────────────────────────────
# COULEURS
# ─────────────────────────────────────────────
R  = "\033[31m"
G  = "\033[32m"
Y  = "\033[33m"
B  = "\033[34m"
M  = "\033[35m"
C  = "\033[36m"
W  = "\033[37m"
BO = "\033[1m"
DIM= "\033[2m"
RST= "\033[0m"

def info(msg):   print(f"{B}ℹ{RST}  {msg}")
def ok(msg):     print(f"{G}✓{RST}  {msg}")
def warn(msg):   print(f"{Y}⚠{RST}  {msg}")
def err(msg):    print(f"{R}✗{RST}  {msg}", file=sys.stderr)
def title(msg):  print(f"\n{BO}{C}{msg}{RST}\n{'─'*len(msg)}")

# ─────────────────────────────────────────────
# CONFIG HELPERS
# ─────────────────────────────────────────────
def load_config():
    if CONFIG_FILE.exists():
        with open(CONFIG_FILE) as f:
            return {**DEFAULT_CONFIG, **json.load(f)}
    return DEFAULT_CONFIG.copy()

def save_config(cfg):
    CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(CONFIG_FILE, "w") as f:
        json.dump(cfg, f, indent=2)
    ok(f"Config sauvegardée dans {CONFIG_FILE}")

def cmd_config(args):
    cfg = load_config()
    title("Configuration")
    
    fields = [
        ("openai_api_key",  "Clé API OpenAI (Whisper)",        True),
        ("mistral_api_key", "Clé API Mistral",                  True),
        ("mistral_model",   "Modèle Mistral",                   False),
        ("output_file",     "Fichier de suivi (.md)",           False),
        ("whisper_mode",    "Mode Whisper [api/local]",         False),
    ]

    for key, label, secret in fields:
        current = cfg.get(key, "")
        display = ("*" * 8 + current[-4:]) if secret and current else (current or "(vide)")
        val = input(f"{label} [{display}] : ").strip()
        if val:
            cfg[key] = val

    save_config(cfg)

# ─────────────────────────────────────────────
# ENREGISTREMENT
# ─────────────────────────────────────────────
def record_audio(cfg) -> str:
    """Enregistre jusqu'à Ctrl+C, retourne le chemin du fichier WAV"""
    try:
        import sounddevice as sd
        import numpy as np
    except ImportError:
        err("sounddevice/numpy non installé : pip install sounddevice numpy")
        sys.exit(1)

    samplerate = cfg.get("samplerate", 44100)
    frames = []
    stop_event = threading.Event()

    def callback(indata, frame_count, time_info, status):
        frames.append(indata.copy())

    title("Enregistrement")
    info("Parlez... Appuyez sur Entrée pour arrêter.")

    stream = sd.InputStream(samplerate=samplerate, channels=1,
                            dtype="float32", callback=callback)
    stream.start()

    # Affichage durée en live
    start = time.time()
    try:
        input()  # attend Entrée
    except KeyboardInterrupt:
        pass
    finally:
        stream.stop()
        stream.close()

    duration = time.time() - start
    ok(f"Enregistrement terminé ({duration:.1f}s)")

    # Sauvegarde WAV
    tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
    tmp.close()

    audio = np.concatenate(frames, axis=0)
    audio_int16 = (audio * 32767).astype(np.int16)
    with wave.open(tmp.name, "wb") as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(samplerate)
        wf.writeframes(audio_int16.tobytes())

    return tmp.name

# ─────────────────────────────────────────────
# TRANSCRIPTION
# ─────────────────────────────────────────────
def transcribe(audio_path, cfg) -> str:
    mode = cfg.get("whisper_mode", "api")
    info(f"Transcription en cours (mode: {mode})...")

    if mode == "api":
        try:
            from openai import OpenAI
        except ImportError:
            err("openai non installé : pip install openai")
            sys.exit(1)
        if not cfg.get("openai_api_key"):
            err("Clé API OpenAI manquante. Lancez : python app.py config")
            sys.exit(1)
        client = OpenAI(api_key=cfg["openai_api_key"])
        with open(audio_path, "rb") as f:
            result = client.audio.transcriptions.create(
                model="whisper-1", file=f, language="fr")
        return result.text

    else:  # local
        try:
            import whisper
        except ImportError:
            err("openai-whisper non installé : pip install openai-whisper torch")
            sys.exit(1)
        model = whisper.load_model("base")
        result = model.transcribe(audio_path, language="fr")
        return result["text"]

# ─────────────────────────────────────────────
# GÉNÉRATION
# ─────────────────────────────────────────────
PROMPT_SYSTEM = """Tu es un assistant spécialisé dans la rédaction de comptes rendus de réunion professionnels.
Tu produis un compte rendu structuré, précis et exploitable, en respectant EXACTEMENT le format fourni.
Tu n'inventes rien. Si une information est absente de la transcription, tu laisses le champ vide ou tu indiques [non mentionné]."""

PROMPT_USER = """Voici la transcription d'une réunion :

---
{transcription}
---

Génère un compte rendu en respectant EXACTEMENT ce format :

---
Date : JJ/MM/AA
Participants :
NOM, Prénom, Rôle

Minutes :
[Retranscription chronologique de tous les éléments clefs abordés.
Inclure les décisions prises ET les éléments qui ont permis de les prendre.]

Actions :
@PERSONNE :
  NOM_TACHE :
    DATE (JJ/MM/AAAA) => description de l'action
---

Exemple de rendu attendu :
---
Date : 19/02/26
Participants :
ROMET, Pierre, PI-Lab
CASANOVA, Raphael, Stagiaire
BALEZEAU, Quentin, Stagiaire

Minutes :
Débrief de l'avancement de Raphael et Quentin sur le travail réalisé avant ce point
Discussion sur les papiers lus par chacun : échanges sur les contenus, les thématiques identifiées et les premières impressions
Point sur l'avancement de la revue de littérature : jusqu'à vendredi 27/02 pour Quentin, jusqu'à vendredi 20/02 pour Raphael
Présentation du format attendu pour le document de synthèse final : Vendredi - 27/02/2026
Discussion sur la structure : Réécriture de la problématique → Schéma en boîtes → Analyse par catégorie → Identification des gaps

Actions :
@Quentin :
  Revue de littérature :
    Jeudi - 19/02/2026 => traiter 1 papier (fiche de synthèse + Excel)
    Vendredi - 20/02/2026 => traiter 1 papier
  Document de synthèse final :
    Vendredi - 27/02/2026 => rendu du document de synthèse final
---

Règles :
- Minutes chronologiques, une idée par ligne
- Actions uniquement si date + personne + tâche explicitement mentionnées
- Respecter l'indentation du format Actions
"""

def generate(transcription, cfg) -> str:
    try:
        import requests
    except ImportError:
        err("requests non installé : pip install requests")
        sys.exit(1)

    if not cfg.get("mistral_api_key"):
        err("Clé API Mistral manquante. Lancez : python app.py config")
        sys.exit(1)

    info(f"Génération du compte rendu ({cfg['mistral_model']})...")

    resp = requests.post(
        "https://api.mistral.ai/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {cfg['mistral_api_key']}",
            "Content-Type": "application/json",
        },
        json={
            "model": cfg["mistral_model"],
            "messages": [
                {"role": "system", "content": PROMPT_SYSTEM},
                {"role": "user", "content": PROMPT_USER.format(transcription=transcription)},
            ],
            "temperature": 0.2,
        },
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]

# ─────────────────────────────────────────────
# SAUVEGARDE
# ─────────────────────────────────────────────
def save_cr(content, cfg):
    filepath = Path(cfg["output_file"])
    separator = "\n\n" + "─" * 60 + "\n\n"
    existing = filepath.read_text(encoding="utf-8") if filepath.exists() else ""
    filepath.write_text(
        content + (separator + existing if existing else ""),
        encoding="utf-8"
    )
    ok(f"Compte rendu sauvegardé → {filepath}")

# ─────────────────────────────────────────────
# COMMANDES
# ─────────────────────────────────────────────
def cmd_run(args):
    cfg = load_config()

    # 1. Source audio
    if args.file:
        audio_path = args.file
        info(f"Fichier audio : {audio_path}")
        tmp_to_delete = None
    else:
        audio_path = record_audio(cfg)
        tmp_to_delete = audio_path

    # 2. Transcription
    if args.transcription:
        # Transcription fournie directement
        transcription = Path(args.transcription).read_text(encoding="utf-8")
        ok("Transcription chargée depuis fichier.")
    else:
        transcription = transcribe(audio_path, cfg)
        ok("Transcription terminée.")

    # Afficher la transcription
    title("Transcription")
    print(DIM + transcription + RST)

    # Option : corriger avant de générer
    if not args.yes:
        edit = input(f"\n{Y}Corriger la transcription avant génération ? [o/N]{RST} ").strip().lower()
        if edit == "o":
            # Ouvre dans $EDITOR ou demande une correction inline
            editor = os.environ.get("EDITOR", "nano")
            with tempfile.NamedTemporaryFile(suffix=".txt", mode="w",
                                             delete=False, encoding="utf-8") as tf:
                tf.write(transcription)
                tf_path = tf.name
            os.system(f"{editor} {tf_path}")
            transcription = Path(tf_path).read_text(encoding="utf-8")
            os.unlink(tf_path)
            ok("Transcription mise à jour.")

    # 3. Génération
    compte_rendu = generate(transcription, cfg)

    # Afficher le résultat
    title("Compte Rendu Généré")
    print(compte_rendu)

    # Option : corriger avant sauvegarde
    if not args.yes:
        edit = input(f"\n{Y}Corriger le compte rendu avant sauvegarde ? [o/N]{RST} ").strip().lower()
        if edit == "o":
            editor = os.environ.get("EDITOR", "nano")
            with tempfile.NamedTemporaryFile(suffix=".md", mode="w",
                                             delete=False, encoding="utf-8") as tf:
                tf.write(compte_rendu)
                tf_path = tf.name
            os.system(f"{editor} {tf_path}")
            compte_rendu = Path(tf_path).read_text(encoding="utf-8")
            os.unlink(tf_path)
            ok("Compte rendu mis à jour.")

    # 4. Sauvegarde
    if not args.yes:
        save = input(f"\n{Y}Sauvegarder dans {cfg['output_file']} ? [O/n]{RST} ").strip().lower()
        if save == "n":
            info("Non sauvegardé.")
            return

    save_cr(compte_rendu, cfg)

    # Cleanup
    if tmp_to_delete and os.path.exists(tmp_to_delete):
        os.unlink(tmp_to_delete)


def cmd_transcribe_only(args):
    """Juste transcrire un fichier, affiche le résultat"""
    cfg = load_config()
    text = transcribe(args.file, cfg)
    print(text)


def cmd_generate_only(args):
    """Générer un CR depuis un fichier texte de transcription"""
    cfg = load_config()
    transcription = Path(args.file).read_text(encoding="utf-8")
    cr = generate(transcription, cfg)
    title("Compte Rendu")
    print(cr)
    save = input(f"\n{Y}Sauvegarder ? [O/n]{RST} ").strip().lower()
    if save != "n":
        save_cr(cr, cfg)


# ─────────────────────────────────────────────
# MAIN
# ─────────────────────────────────────────────
def main():
    parser = argparse.ArgumentParser(
        description="🎙️  Générateur de compte rendu de réunion",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Exemples :
  python app.py                      # enregistre le micro → transcrit → génère
  python app.py -f réunion.mp3       # depuis un fichier audio
  python app.py -y                   # mode non-interactif (pas de confirmations)
  python app.py transcribe -f audio.mp3     # transcription seule
  python app.py generate -f notes.txt       # génération depuis texte
  python app.py config                      # configurer les clés API
        """
    )

    subparsers = parser.add_subparsers(dest="cmd")

    # Commande par défaut : run
    parser.add_argument("-f", "--file", help="Fichier audio source (sinon enregistrement micro)")
    parser.add_argument("-t", "--transcription", help="Fichier texte de transcription (skip Whisper)")
    parser.add_argument("-y", "--yes", action="store_true", help="Mode non-interactif")

    # Sous-commandes
    sub_config = subparsers.add_parser("config", help="Configurer les clés API")

    sub_trans = subparsers.add_parser("transcribe", help="Transcrire un fichier audio seulement")
    sub_trans.add_argument("-f", "--file", required=True, help="Fichier audio")

    sub_gen = subparsers.add_parser("generate", help="Générer CR depuis un fichier texte")
    sub_gen.add_argument("-f", "--file", required=True, help="Fichier texte de transcription")

    args = parser.parse_args()

    if args.cmd == "config":
        cmd_config(args)
    elif args.cmd == "transcribe":
        cmd_transcribe_only(args)
    elif args.cmd == "generate":
        cmd_generate_only(args)
    else:
        cmd_run(args)


if __name__ == "__main__":
    main()
