#!/usr/bin/env python3 
# -*- coding: utf-8 -*-
"""
AI処理専用：講義テキスト(文字起こし)とスライドMarkdownから教科書用/スライド用Markdownを生成します。
- OpenAIとGoogle(Gemini)でmessages構築を分岐。
- OpenAI: system + userの会話形式
- Google: 1つのuserメッセージにまとめる形式
- AIに渡すmessagesを .log ファイルに保存
"""

import os
import sys
import argparse
import re
import json
from pathlib import Path

try:
    import google.generativeai as genai
    from openai import OpenAI
except ImportError:
    print("必要なライブラリがインストールされていません。", file=sys.stderr)
    print("pip install google-generativeai openai python-dotenv を実行してください。", file=sys.stderr)
    input("\nPress ENTER to terminate>>\n")
    sys.exit(1)

from tkai_lib import read_ai_config


PROMPT_TEMPLATE_JA = None
PROMPT_TEMPLATE_EN = None

language_map = {
    'jp': '日本語',
    'ja': '日本語',
    'en': '米国英語',
    'cn': '標準中国語',
    'zh': '標準中国語',
    'kr': '韓国語',
    'ko': '韓国語',
    }

pause = 0


def terminate():
    if pause:
        input("\nPress ENTER to terminate>>\n")
    exit()

def search_file(infile=None):
    script_path = os.path.abspath(sys.argv[0])
    script_dir = os.path.dirname(script_path)
    script_name = os.path.splitext(os.path.basename(script_path))[0]
    default_ini = f"{script_name}.ini"

    if infile is None:
        for path in [os.getcwd(), script_dir]:
            candidate = os.path.join(path, default_ini)
            if os.path.isfile(candidate):
                return candidate
        return None

    if not os.path.isfile(infile):
        candidate = os.path.join(script_dir, infile)
        if os.path.isfile(candidate):
            return candidate
        return None

    return infile

def read_ini(inifile=None):
    path = search_file(inifile)
    if path is None:
        raise FileNotFoundError("INIファイルが見つかりませんでした")

    result = {}
    variables = {}
    current_key = None
    multiline_val = []
    multiline_delim = None

    with open(path, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.rstrip()

            if not line or line.startswith('#') or line.startswith(';'):
                continue

            # 複数行値の終了判定（stripで判定）
            if multiline_delim:
                if line.strip() == multiline_delim:
                    val = '\n'.join(multiline_val)
                    result[current_key] = val
                    variables[current_key] = val
                    current_key = None
                    multiline_val = []
                    multiline_delim = None
                else:
                    multiline_val.append(line)
                continue

            # key=val の解析
            if '=' in line:
                key, val = map(str.strip, line.split('=', 1))
                val = val.strip()

                # 複数行値の開始判定（空文字でも対応）
                if (val == '"""' or val == "'''" or
                   (val.startswith('"""') and not val.endswith('"""')) or \
                   (val.startswith("'''") and not val.endswith("'''")) ):
                    multiline_delim = val[:3]
                    content = val[3:]
                    multiline_val = [content] if content else []
                    current_key = key
                    continue

                # 単一行の複数行値
                if (val.startswith('"""') and val.endswith('"""')) or \
                   (val.startswith("'''") and val.endswith("'''")):
                    val = val[3:-3]

                result[key] = val
                variables[key] = val

    # 変数展開（あとから一括処理）
    for key, val in result.items():
        def expand_var(match):
            var_name = match.group(1)
            return variables.get(var_name, match.group(0))
        result[key] = re.sub(r"\$(\w+)\b", expand_var, val)

    return result


def parse_args():
    global PROMPT_TEMPLATE_JA, PROMPT_TEMPLATE_EN

    read_ai_config('ai.env')

    p = argparse.ArgumentParser(
        description="講義の文字起こしとスライドをAIで教科書/スライドMarkdownに変換（Pandoc不要）。",
        formatter_class=argparse.RawTextHelpFormatter
    )
    p.add_argument('--inifile', default = None, help='プロンプトなどを保存したkey=valファイル')
    p.add_argument('-i',  '--infile',   default = None, help='文字起こしテキストファイル (例: lecture.txt)')
    p.add_argument('-im', '--in_slide', default = None, help='入力 講義スライドMarkdown (任意, 例: slide.md)')
    p.add_argument('-t',  '--textbook', help='出力 教科書Markdown (デフォルト: [infile]_textbook.md)')
    p.add_argument('-s',  '--slide',    help='出力 スライドMarkdown (デフォルト: [infile]_slide.md)')

    ai = p.add_argument_group('AI設定')
    ai.add_argument('--api', '-a', choices=['gemini', 'openai5', 'openai', 'google'], default='gemini', help='使用API')
    ai.add_argument('--model', help='明示モデル名の指定（apiごとに適用先を切替）')
    ai.add_argument('--openai_model', default=os.getenv("OPENAI_MODEL", "gpt-4o"))
    ai.add_argument('--openai_model5', default=os.getenv("OPENAI_MODEL5", "gpt-5.2"))
#    ai.add_argument('--google_model', default=os.getenv("GOOGLE_MODEL", "gemini-3-preview"))
    ai.add_argument('--google_model', default=os.getenv("GOOGLE_MODEL", "gemini-2.5-flash"))

    ai.add_argument('--lang', default='ja', choices=['ja', 'en', 'zh', 'ko'], help='出力言語 (デフォルト ja)')
    ai.add_argument('--field', default='半導体工学', help='専門分野')
    ai.add_argument('--role', default='大学教授', help='役割')
    ai.add_argument('--pause', type=int, default=0, help="終了時にENTERキー入力を要求するか (デフォルト: 0)")
    args = p.parse_args()
    
    if args.infile is None and args.in_slide is None:
        print("❌ --infile(-i)か--in_slide(-im)のどちらかを与えないといけません")
        terminate()

    args.openai_key = os.getenv("OPENAI_API_KEY")
    args.gemini_key = os.getenv("GOOGLE_API_KEY")

    if args.model:
        if args.api == 'openai5': args.openai_model5 = args.model
        elif args.api == 'openai': args.openai_model = args.model
        elif args.api in ('gemini', 'google'): args.google_model = args.model

    args.inifile = search_file(args.inifile)
    print("Prompot inifile: ", args.inifile)
    inf = read_ini(args.inifile)
    PROMPT_TEMPLATE_JA = inf["PROMPT_TEMPLATE_JA"]
    PROMPT_TEMPLATE_EN = inf["PROMPT_TEMPLATE_EN"]

    return p, args

def build_messages(api_choice: str, system_instructions: str, lecture_text: str, slide_markdown: str, final_instruction: str):
    """OpenAI: 会話形式, Google: 1メッセージ方式"""
    messages = []

    if api_choice in ('openai', 'openai5'):
        # OpenAI形式
        messages.append({"role": "system", "content": system_instructions})

        if lecture_text:
            messages.append({"role": "user", "content": f"# 文字起こしテキスト\n\n{lecture_text}"})
        else:
            messages.append({"role": "user", "content": "文字起こしテキストはありません。"})

        if slide_markdown:
            messages.append({"role": "user", "content": f"# 講義スライド\n\n{slide_markdown}"})

        messages.append({"role": "user", "content": final_instruction})

    elif api_choice in ('gemini', 'google'):
        # Google/Gemini形式: まとめる
        parts = []
        parts.append(system_instructions)

        if lecture_text:
            parts.append(f"# 文字起こしテキスト\n\n{lecture_text}")
        else:
            parts.append("文字起こしテキストはありません。")

        if slide_markdown:
            parts.append(f"# 講義スライド\n\n{slide_markdown}")

        parts.append(final_instruction)

        messages = [{"role": "user", "content": "\n\n".join(parts)}]

    return messages


def save_messages_log(messages, infile: str):
    """messages を JSON 形式でログファイルに保存"""
    if infile:
        base = Path(infile).stem
    else:
        base = "output"
    log_file = f"{base}.log"

    try:
        with open(log_file, "w", encoding="utf-8") as f:
            json.dump(messages, f, ensure_ascii=False, indent=2)
        print(f"📝 messagesログを '{log_file}' に保存しました")
    except Exception as e:
        print(f"⚠️ ログ保存に失敗しました: {e}", file=sys.stderr)

def call_ai_api(messages: list, api_choice: str, *, openai_key=None, openai_model=None,
                openai_model5=None, gemini_key=None, gemini_model=None) -> str:
    """
    各社APIを呼び出す。マルチターンメッセージリストを適切な形式で渡す。
    """

    model_name = ""
    try:
        if api_choice in ('gemini', 'google'):
            model_name = gemini_model
            if not gemini_key: raise ValueError("GOOGLE_API_KEY が未設定です。")
            
            print(f"🚀 Gemini API [{model_name}] を呼び出しています...")
            genai.configure(api_key=gemini_key)
            model = genai.GenerativeModel(model_name)
            
            # Gemini形式のメッセージリストに変換: 'content' -> 'parts'
            gemini_messages = [{"role": m["role"], "parts": [m["content"]]} for m in messages]
            response = model.generate_content(gemini_messages)
            return response.text

        elif api_choice == 'openai5':
            model_name = openai_model5
            if not openai_key: raise ValueError("OPENAI_API_KEY が未設定です。")

            print(f"🚀 OpenAI Responses API (openai5) [{model_name}] を呼び出しています...")
            client = OpenAI(api_key=openai_key)
            response = client.responses.create(
                model=model_name,
                input=messages # このAPIがメッセージリスト形式を受け付けると仮定
            )
            return response.output_text or ""

        elif api_choice == 'openai':
            model_name = openai_model
            if not openai_key: raise ValueError("OPENAI_API_KEY が未設定です。")

            print(f"🚀 OpenAI Chat Completions API [{model_name}] を呼び出しています...")
            client = OpenAI(api_key=openai_key)
            response = client.chat.completions.create(
                model=model_name,
                messages=messages
            )
            return response.choices[0].message.content

        else:
            raise ValueError(f"未対応API: {api_choice}")

    except Exception as e:
        print(f"❌ API呼び出し中にエラーが発生しました ({api_choice}/{model_name}): {e}", file=sys.stderr)
        return None

def run_ai_processing(infile: str, in_slide_file: str, textbook_file: str, slide_file: str, args):
    """AI処理を実行し、ファイルを出力"""

    print()
    print(f"生成AIを実行します...")
    print(f"  文字起こし入力: {infile}")
    print(f"  スライド入力  : {in_slide_file}")
    print(f"  出力言語: {args.lang}")
    print()

    if infile:
        try:
            lecture_text = Path(infile).read_text(encoding='utf-8')
        except FileNotFoundError:
            print(f"❌ 入力ファイル '{infile}' が見つかりません", file=sys.stderr)
            terminate()
    else:
        lecture_text = None

    slide_markdown = ""
    if in_slide_file:
        try:
            slide_markdown = Path(in_slide_file).read_text(encoding='utf-8')
            print(f"📄 スライド入力: {in_slide_file}")
        except FileNotFoundError:
            print(f"⚠️ スライド '{in_slide_file}' が見つかりません", file=sys.stderr)
            terminate()

    if args.lang == 'en':
        language = '米国英語' 
        if PROMPT_TEMPLATE_EN:
            prompt_template = PROMPT_TEMPLATE_EN
        else:
            print(f"Error: PROMPT_TEMPLATE_EN is not provided for lang=en\n")
            terminate()
    elif args.lang == 'ja':
        language = '日本語'
        prompt_template = PROMPT_TEMPLATE_JA
    elif args.lang == 'zh':
        language = '標準中国語'
        prompt_template = PROMPT_TEMPLATE_JA
    elif args.lang == 'ko':
        language = '韓国語'
        prompt_template = PROMPT_TEMPLATE_JA
    else:
        if args.lang in language_map.keys():
            language = language_map[args.lang]
            prompt_template = PROMPT_TEMPLATE_JA
        else:
            print(f"Error: Invalid lang={args.lang}\n")
            terminate()

    prompt = prompt_template\
        .replace("{field}", args.field)\
        .replace("{role}", args.role)\
        .replace("{language}", language)\
        .strip()
#    print("  prompt:", prompt)
    system_instructions = prompt
    
    final_instruction = """
すべての情報を統合し、以下の形式で出力してください:

[TEXTBOOK_START]
（教科書の内容）
[TEXTBOOK_END]
[SLIDES_START]
（スライドの内容）
[SLIDES_END]
"""

    messages = build_messages(args.api, system_instructions, lecture_text, slide_markdown, final_instruction)

    # 🔹 messagesログを保存
    save_messages_log(messages, infile)

    print("\n--- 🤖 AIにプロンプトを送信 ---")

    ai_response = call_ai_api(
        messages,
        args.api,
        openai_key=args.openai_key,
        openai_model=args.openai_model,
        openai_model5=args.openai_model5,
        gemini_key=args.gemini_key,
        gemini_model=args.google_model,
    )

    if not ai_response:
        print("❌ Error: 生成AIからの応答が得られません", file=sys.stderr)
        terminate()

    textbook_match = re.search(r"\[TEXTBOOK_START\](.*?)\[TEXTBOOK_END\]", ai_response, re.DOTALL)
    slides_match   = re.search(r"\[SLIDES_START\](.*?)\[SLIDES_END\]", ai_response, re.DOTALL)
    
    if not textbook_match or not slides_match:
        print("❌ Error: 出力形式エラー", file=sys.stderr)
        print(ai_response, file=sys.stderr)
        terminate()

    Path(textbook_file).write_text(textbook_match.group(1).strip(), encoding='utf-8')
    print(f"✅ 教科書ファイル '{textbook_file}' を生成")
    Path(slide_file).write_text(slides_match.group(1).strip(), encoding='utf-8')
    print(f"✅ スライドファイル '{slide_file}' を生成")


def main():
    global pause
    
    parser, args = parse_args()
    pause = args.pause
    base = Path(args.infile).stem if args.infile else "output"
    textbook = args.textbook or f"{base}_textbook.md"
    slide    = args.slide    or f"{base}_slide.md"

    run_ai_processing(args.infile, args.in_slide, textbook, slide, args)

    terminate()


if __name__ == "__main__":
    main()
    terminate()
