from fastapi import APIRouter, HTTPException, Query # 진철 추가 (Query)
from fastapi.responses import RedirectResponse
import boto3
import os
import time
from schemas import SpeechText, UserQuestion, RobotRequest
from fastapi import BackgroundTasks
from fastapi import Body
from fastapi import UploadFile, File
from db import database_demo
from openai import OpenAI
from config import logger, openai_api_key
import re
import httpx
import datetime
import db_module
import uuid

def normalize_text(text: str) -> str:
    # \n, \r, 스페이스(半角/全角) 전부 제거
    return re.sub(r"\s+", "", text).strip()

# 🔧 S3 설정
s3_client = boto3.client('s3')
bucket_name = 'shanri-ai-chatbot-for-text-to-speech'
client = OpenAI(api_key=openai_api_key)

router = APIRouter()

# ✅ 음성 합성
async def synthesize_speech(text, user_id):
    response = client.audio.speech.create(
        model="tts-1",
        voice="nova",
        input=text,
    )
    audio_file = f"tmp/audio-{user_id}-{time.time()}.mp3"
    with open(audio_file, 'wb') as f:
        for chunk in response.iter_bytes():
            f.write(chunk)
    s3_key = f"{user_id}-{time.time()}.mp3"
    s3_client.upload_file(audio_file, bucket_name, s3_key)
    os.remove(audio_file)
    return f"https://{bucket_name}.s3.amazonaws.com/{s3_key}"


# ✅ 건강 체크
@router.get("/health")
async def health_check():
    return {"status": "healthy"}

# ✅ 음성 요청 처리
@router.post("/apige/speech")
async def speech(speech_text: SpeechText):
    text = speech_text.text
    chat_token = speech_text.chat_token
    if not text:
        raise HTTPException(status_code=400, detail="Text is required")
    audio_file = await synthesize_speech(text, chat_token)
    return {"audio_file": audio_file}

# 📌 질문-답변 매핑 시나리오
QA_CHAT_SCENARIO = {
    "コーディングを学んでみたいです。": ["以前にコーディングを学んだことはありますか？"],
    "いいえ、学んだことはありません。": ["""はい、それではコーディングの基本要素を使って掲示板を作るプロセスを進めてみましょう。
## 技術スタック整理

### フロントエンド（ユーザーが見る画面）
- **HTML**：掲示板の構造（投稿一覧、投稿フォーム、コメント欄）
- **CSS**：デザイン（レイアウト、ボタン、色、レスポンシブ）
- **JavaScript**：
  - ボタンやフォームの動作制御
  - 投稿一覧・詳細を表示するときにサーバーと通信（Fetch API）

---

### バックエンド（サーバー）
- **Python + FastAPI**
  - ルーター：投稿一覧取得、新規投稿、編集/削除、コメント機能
  - JSON 形式でデータをやり取り
- **MySQL**
  - 投稿・コメント・ユーザー情報を保存
  - 基本的な SQL 文（INSERT, SELECT, UPDATE, DELETE）のみ学習

---

### サーバー環境
- **AWS EC2 (Ubuntu)**

---

## カリキュラムの難易度調整
- **ステップ 1（基礎）**：静的な HTML/CSS で掲示板レイアウトを作成
- **ステップ 2（バックエンド基本）**：FastAPI で投稿一覧/投稿作成 API を作成
- **ステップ 3（DB 連携）**：MySQL に投稿を保存して取得
- **ステップ 4（フロントとバック接続）**：JS Fetch で API と接続して動作確認
- **ステップ 5（追加機能）**：コメント、ページネーション、簡単なログイン機能""","1日ごとの学習内容も作成しましょうか？"],
    "はい、お願いします。": ["""## 3ヶ月 (12週間) 掲示板プロジェクト学習スケジュール

## **1ヶ月目（基礎編：Webの土台を作る）**

### **Week 1 — HTML 基礎**
- **Day 1**: Webとは？ ブラウザとサーバーの仕組み
- **Day 2**: HTML 基本文法（タグ、要素、属性）
- **Day 3**: 見出し・段落・リンク・画像の使い方
- **Day 4**: リスト・テーブル・フォーム
- **Day 5**: 掲示板レイアウト（一覧ページの雛形作成）

### **Week 2 — CSS デザイン**
- **Day 6**: CSS の基本（セレクタ、色、フォント）
- **Day 7**: レイアウト（Boxモデル、margin/padding/border）
- **Day 8**: Flexboxでのレイアウト実践
- **Day 9**: レスポンシブデザイン（スマホ対応）
- **Day 10**: 掲示板一覧の見た目を整える演習

### **Week 3 — JavaScript 入門**
- **Day 11**: JSの基本（変数、型、演算）
- **Day 12**: 条件分岐とループ
- **Day 13**: 関数とイベント処理（クリック/入力）
- **Day 14**: DOM操作（要素の取得、変更、追加）
- **Day 15**: 掲示板で「ボタンを押すとメッセージが出る」演習

### **Week 4 — JS応用 + ミニプロジェクト**
- **Day 16**: 配列とオブジェクト
- **Day 17**: JSONの理解（データの形）
- **Day 18**: Fetch API で外部データを取得（疑似API利用）
- **Day 19**: 掲示板「仮の投稿一覧」を JSで表示する演習
- **Day 20**: 1ヶ月目まとめ（HTML/CSS/JSだけで簡易掲示板）

---

## **2ヶ月目（バックエンド編：FastAPI + DB）**

### **Week 5 — Python 基礎**
- **Day 21**: Python 基本文法（print, 変数, 型）
- **Day 22**: 条件分岐 if/elif/else
- **Day 23**: ループ for/while
- **Day 24**: 関数定義とモジュール
- **Day 25**: 簡単なミニプログラム作成（計算機など）

### **Week 6 — FastAPI 入門**
- **Day 26**: FastAPIとは？ REST APIの基本
- **Day 27**: FastAPI 環境構築 & Hello World
- **Day 28**: GETエンドポイント（投稿一覧を返す）
- **Day 29**: POSTエンドポイント（新規投稿を受け取る）
- **Day 30**: APIドキュメント（Swagger UI）を確認

### **Week 7 — MySQL 基礎**
- **Day 31**: RDBの基本概念（テーブル・レコード・列）
- **Day 32**: SQL文（SELECT / INSERT）
- **Day 33**: UPDATE / DELETE
- **Day 34**: 掲示板用テーブル設計（users, posts, comments）
- **Day 35**: MySQL + FastAPI接続テスト

### **Week 8 — FastAPI + MySQL 実装**
- **Day 36**: 投稿一覧をDBから取得
- **Day 37**: 新規投稿をDBに保存
- **Day 38**: 投稿詳細ページAPI
- **Day 39**: 投稿編集・削除API
- **Day 40**: 2ヶ月目まとめ（FastAPI + MySQLで掲示板CRUD完成）

---

## **3ヶ月目（発展編：つなげる & 改良する）**

### **Week 9 — フロントとバックの連携**
- **Day 41**: JSからFastAPIにFetchでリクエスト
- **Day 42**: 投稿一覧をAPIから取得して表示
- **Day 43**: 投稿フォームからAPIへ送信して保存
- **Day 44**: 投稿詳細をAPIから取得して表示
- **Day 45**: 掲示板の基本操作が一通りできる状態に

### **Week 10 — コメント機能 & ページング**
- **Day 46**: コメント用API作成
- **Day 47**: コメント一覧/追加のフロント実装
- **Day 48**: ページングの考え方（limit/offset）
- **Day 49**: ページ切り替えボタンの実装
- **Day 50**: コメント & ページング統合テスト

### **Week 11 — 簡易ログイン機能**
- **Day 51**: ユーザー登録API（ID, PW）
- **Day 52**: パスワードハッシュ化の基本
- **Day 53**: ログインAPI（JWTトークン）
- **Day 54**: 投稿時にユーザー情報を紐づける
- **Day 55**: 自分の投稿だけ編集/削除可能にする

### **Week 12 — まとめ & 発表**
- **Day 56**: デザイン改善（CSS仕上げ）
- **Day 57**: エラー処理 & バリデーション追加
- **Day 58**: AWSサーバーでデプロイ & IPアクセス確認
- **Day 59**: 掲示板完成発表用スライド作成
- **Day 60**: 成果発表会（チーム or 個人発表）

---

## 学習ゴール
- **1ヶ月目**：HTML/CSS/JSで静的掲示板を作れる
- **2ヶ月目**：FastAPI + MySQLで掲示板APIを作れる
- **3ヶ月目**：フロントとAPIをつなげて本物の掲示板完成""",
"""以下は、掲示板作成の3か月コースにおける評価項目です。
- 学習スケジュールに沿って進められたか
- 各週の学習目標を達成したか
- 掲示板を完成させたか"""]
}

QA_LOG_SCENARIO = {
    "今日はPythonの条件分岐（if文）と繰り返し（for文／while文）を勉強しました。": [
        "条件分岐（if）と繰り返し（for／while）の学習はいかがでしたか？"
    ],
    "条件分岐（if）と繰り返し（for／while）は初めてでかなり難しかったですが、検索して調べながら一つひとつ理解して学習しました。また、例題を解きつつ、実際にコードも書いてみました。": [
        "日誌を整理しましょうか？"
    ],
    "はい、お願いします":["""## 📘 学習日誌 (OOOO-OO-OO)

### 今日学んだ内容
- **Python 条件文 (IF)**
- **繰り返し文 (For / While)**

### 学習過程と感想
- 条件文 IF と繰り返し文 For/While は初めて学ぶ内容だったので難しく感じた。
- 検索して調べながら一つひとつ理解して学習を進めた。
- 練習問題を解きながら、実際にコードを書いて確認できた。

### 成果
- Python の基本的な制御文（条件文・繰り返し文）を理解できた。
- 簡単なコードの作成と問題演習を経験。

### 今後の計画
- 条件文や繰り返し文を使ったより多様な練習問題に取り組む。
- 入れ子の条件文／繰り返し文、break/continue などの発展的な内容を学習予定。"""]
}

QA_LOG_WORKER_SCENARIO = {
    "今日はPythonの条件分岐（if文）と繰り返し（for文／while文）を勉強しました。": [
        "条件分岐（if）と繰り返し（for／while）の学習はいかがでしたか？"
    ],
    "条件分岐（if）と繰り返し（for／while）は初めてでかなり難しかったですが、検索して調べながら一つひとつ理解して学習しました。また、例題を解きつつ、実際にコードも書いてみました。": [
        "日誌を整理しましょうか？"
    ],
    "はい、お願いします":["""## 📘 学習日誌 (OOOO-OO-OO)

### 今日学んだ内容
- **Python 条件文 (IF)**
- **繰り返し文 (For / While)**

### 学習過程と感想
- 条件文 IF と繰り返し文 For/While は初めて学ぶ内容だったので難しく感じた。
- 検索して調べながら一つひとつ理解して学習を進めた。
- 練習問題を解きながら、実際にコードを書いて確認できた。

### 成果
- Python の基本的な制御文（条件文・繰り返し文）を理解できた。
- 簡単なコードの作成と問題演習を経験。

### 今後の計画
- 条件文や繰り返し文を使ったより多様な練習問題に取り組む。
- 入れ子の条件文／繰り返し文、break/continue などの発展的な内容を学習予定。""","グループチャットに共有しましょうか？"],
"はい。":["""承知しました！
グループチャットに共有します。"""]
}

QA_CHAT_WORKER_SCENARIO = {
    "IT企業に就職したいです。":["""IT企業に就職をご希望なのですね！

それでは3つの方法があります。

1. Paiza Bランクの取得
2. プロジェクト（PJT）の実施
3. Paiza Bランクの取得＋プロジェクトの実施

一番おすすめする方法は、3番目です！"""],

"PJTを進めます。": ["""【プロジェクト名】
ガチャプロジェクト

【プロジェクト概要】
ユーザーがガチャ商品を登録し、掲示板に投稿してユーザー同士が交流でき、さらに地図を押すと自分の周囲のガチャショップを表示するプロジェクトです。
"""],

"ガチャプロジェクトをやってみます。":["""はい、次はガチャプロジェクトの1か月スケジュールを作成いたします。
# 1か月プロジェクトスケジュール（ガチャプロジェクト）

## 第1週 — 企画 & 開発環境構築

- **要件整理**
  - 核心機能の確定:
    - ガチャ商品の登録
    - 掲示板／コメント機能
    - ユーザー同士の交流（コメント／いいね）
    - 地図上で周辺のガチャショップ表示（Google Maps / Leaflet 等）
    - 会員登録／ログイン

- **DB設計**
  - ERD 設計: `users`, `gacha_items`, `posts`, `comments`, `shops`, `likes` などのテーブル定義

- **開発環境構築**
  - GitHub/GitLab リポジトリ作成
  - バックエンドフレームワーク選定（例: FastAPI, Node.js, Spring 等）
  - フロントエンド環境構築（React, Vue, Next.js 等）
  - DB（MySQL, PostgreSQL）接続確認

- **UIワイヤーフレーム作成**
  - ログイン／会員登録、商品登録画面、掲示板、地図ページの初期デザイン（Figma 等）

---

## 第2週 — コア機能開発（DB連携）

- **会員／認証機能**
  - 会員登録、ログイン、JWT 認証設定

- **ガチャ商品登録機能**
  - 商品の登録／参照／削除 API
  - 画像アップロード（S3, Cloud Storage 活用可）

- **掲示板 & コメント機能**
  - 投稿 CRUD
  - コメント、いいね機能実装

- **DBマイグレーション**
  - 初期サンプルデータ挿入

---

## 第3週 — 地図機能 & フロントエンド統合

- **地図機能**
  - Google Maps API または Leaflet.js でガチャショップ位置表示
  - 現在地を基準にした周辺ショップリスト表示
  - DB に座標を保存して連携

- **フロントエンド**
  - 基本レイアウト適用（ヘッダー、フッター、メニュー）
  - 商品登録ページ連携
  - 掲示板 UI、コメント UI 連携
  - 地図ページ UI 実装

---

## 第4週 — テスト & デプロイ

- **統合テスト**
  - API テスト（Postman, pytest 等）
  - フロントエンド・バックエンド連携テスト

- **バグ修正 & 最適化**
  - UX 改善（ローディング表示、エラーメッセージ処理）
  - DB インデックス最適化

- **デプロイ**
  - AWS EC2 + Nginx/Apache でサービス公開
  - HTTPS 証明書適用（Let’s Encrypt）

- **最終チェック**
  - サンプルユーザーデータで E2E シナリオテスト
  - デモ準備
""","""ガチャプロジェクトの評価基準です。
- プロジェクトスケジュールに沿って進められたか
- 各週ごとの目標を達成できたか
""","1日ごとのスケジュールもお作りしましょうか？"],

"はい、お願いします。":["""# ガチャプロジェクト — 日別開発スケジュール

## ✅ 第1週（企画 & 環境構築）

- **Day 1** : プロジェクトキックオフ
  - 要件整理、核心機能の確定（会員、掲示板、ガチャ商品、地図）
  - 機能の優先順位を決定

- **Day 2** : DB設計
  - ERD 作成（`users`, `gacha_items`, `posts`, `comments`, `shops`, `likes`）
  - テーブルカラム定義

- **Day 3** : 開発環境準備
  - GitHub/GitLab リポジトリ作成
  - Python/Node.js 環境構築、仮想環境準備

- **Day 4** : バックエンド基本設定
  - FastAPI/Express プロジェクト初期化
  - DB（MySQL/PostgreSQL）接続テスト

- **Day 5** : フロントエンド基本設定
  - React/Next.js プロジェクト初期化
  - ルーティング構造設定（ログイン、掲示板、地図など）

---

## ✅ 第2週（コア機能開発）

- **Day 6** : 会員／認証 (1)
  - 会員登録 API、DB 保存

- **Day 7** : 会員／認証 (2)
  - ログイン API、JWT 発行／検証

- **Day 8** : ガチャ商品 (1)
  - 商品登録 API
  - 商品テーブル連携

- **Day 9** : ガチャ商品 (2)
  - 商品参照／削除 API
  - 画像アップロード（S3／Cloud Storage）

- **Day 10** : 掲示板 (1)
  - 投稿作成／参照 API

---

## ✅ 第3週（機能拡張 & UI 連携）

- **Day 11** : 掲示板 (2)
  - 投稿編集／削除 API
  - コメント機能追加

- **Day 12** : いいね機能
  - いいね API、DB 反映

- **Day 13** : 地図機能 (1)
  - Leaflet.js／Google Maps API 連携
  - 現在地の表示

- **Day 14** : 地図機能 (2)
  - DB に保存されたガチャショップを表示
  - 地図と API を連携

- **Day 15** : フロント UI 基本枠組み
  - ヘッダー／フッター／メニュー適用
  - ページ遷移確認

---

## ✅ 第4週（テスト & デプロイ）

- **Day 16** : フロント・バック連携 (1)
  - 会員登録／ログイン画面 + API 連携

- **Day 17** : フロント・バック連携 (2)
  - 掲示板画面 + API 連携
  - コメント／いいね UI

- **Day 18** : フロント・バック連携 (3)
  - ガチャ商品登録／参照画面 + API 連携

- **Day 19** : フロント・バック連携 (4)
  - 地図画面 + API 連携

- **Day 20** : 統合テスト & デプロイ
  - API テスト（Postman）
  - AWS EC2 デプロイ、HTTPS 適用
  - デモ準備  """],

  "3番で進めます。": ["現在、ITに関する経験や知識はお持ちですか？"],

  "Pythonの基礎知識を学びました。":["""はい、それではPaiza Bランクの1か月コースを作成いたします。
# Paiza Bランク対策 1ヶ月学習スケジュール（Python基礎知識あり）

## 🔹 1週目: Paiza Cランク総復習
**目標**: 基礎問題を素早く解けるようにする

- 入出力処理の練習（複数行入力、2次元入力）
- 文字列処理（split, join, count, find, replace）
- リスト操作（最大値/最小値、ソート、スライス）
- 簡単なシミュレーション（座標移動、繰り返し処理）
- **演習**: Paiza Cランク問題を毎日3問

---

## 🔹 2週目: シミュレーション & カウント強化
**目標**: Bランク典型パターンの習得

- **シミュレーション**
  - 状態更新（例: ロボット移動、ゲーム盤の変化）
  - 【問題①】チョコレート座標シミュレーション
- **カウント**
  - 条件を満たす数を数える
  - 行列やビンゴ判定
  - 【問題②】N×Nビンゴ
- **演習**: Paiza C～Bランク問題を毎日2問

---

## 🔹 3週目: 全探索 & 最小値探索
**目標**: Brute Force で漏れなく解ける力

- **探索**
  - 全探索（Brute Force）
  - マンハッタン距離の計算
  - 最小値の更新と候補の保存
  - 【問題③】映画館の座席
- **演習**:
  - Cランクシミュレーション1問
  - Bランク探索系問題1問（毎日合計2問）

---

## 🔹 4週目: 実戦模擬試験 & オーバービュー
**目標**: 実際のBランク試験形式に慣れる

- **模擬試験**
  - 制限時間1時間で1問解く
  - 入力処理 → ロジック → 出力を素早く
- **復習**
  - 間違えた問題を必ず再挑戦
  - コードをリファクタリング（短く、読みやすく）
- **演習**:
  - Bランク問題を毎日1問
  - 模擬試験形式で週3回

---

# ✅ 学習ポイントまとめ
- **Week1**: Cランクの基礎を固める
- **Week2**: シミュレーション/カウント練習
- **Week3**: 探索・最小値問題に慣れる
- **Week4**: 模擬試験で実戦感覚をつける

[【Paizaのリンク】](https://paiza.jp/)
""","1日ごとのスケジュールもお作りしましょうか？"],

"はい、1日ごとのスケジュールをお願いします。":["""# Paiza Bランク対策 1ヶ月 日別学習スケジュール（Python基礎知識あり）

## 🔹 第1週 (Cランク総復習)
- **Day 1**: 入出力復習（`input().split()`、複数行入力、2次元リスト入力）
- **Day 2**: 文字列処理基礎（`split`, `join`, `replace`）＋演習（文字列の逆順）
- **Day 3**: 文字列探索（`find`, `count`, `in`）＋演習（単語の出現回数を数える）
- **Day 4**: リスト復習（最大/最小、ソート、スライス）
- **Day 5**: Cランク問題3問解く（文字列・リスト中心）
- **週末（復習）**: Day1～5のコード再実行、誤答ノート作成

---

## 🔹 第2週 (シミュレーション & カウント強化)
- **Day 6**: シミュレーション基礎（座標移動、状態更新）
- **Day 7**: 演習 – チョコレート座標シミュレーション（問題①）
- **Day 8**: カウント基礎（条件を満たす数を数える）
- **Day 9**: 演習 – N×Nビンゴ（問題②）
- **Day 10**: C～Bランク混合問題2問解く（シミュレーション/カウント）
- **週末（復習）**: チョコレート問題、ビンゴ問題を再挑戦（異なる入力でテスト）

---

## 🔹 第3週 (探索 & 最小値探索)
- **Day 11**: 全探索（Brute Force）基礎＋簡単なリスト探索演習
- **Day 12**: マンハッタン距離計算演習
- **Day 13**: 演習 – 映画館の座席（問題③）
- **Day 14**: 応用 – 最小距離の座標を複数出力する問題
- **Day 15**: Bランク探索問題2問解く（映画館座席＋応用問題）
- **週末（復習）**: 映画館座席問題を再挑戦、コード最適化

---

## 🔹 第4週 (実戦模擬試験 & 総復習)
- **Day 16**: 模擬試験 – 制限時間1時間でシミュレーション問題1問
- **Day 17**: 誤答復習＋コードリファクタリング
- **Day 18**: 模擬試験 – 制限時間1時間でカウント問題1問
- **Day 19**: 誤答復習＋コード最適化
- **Day 20**: 模擬試験 – 制限時間1時間で探索問題1問
- **週末（復習）**: 模擬試験3問を再挑戦、間違えた部分を整理

---

## 🔹 最終週 (仕上げ & 最終チェック)
- **Day 21**: Bランク過去問1問（シミュレーション）
- **Day 22**: Bランク過去問1問（カウント）
- **Day 23**: Bランク過去問1問（探索）
- **Day 24**: ランダムでC/B問題2問解く
- **Day 25**: 総復習＋模擬試験1時間（ランダムBランク問題）
- **週末（最終復習）**: 今まで解いた問題の誤答をすべて再挑戦

---

# ✅ スケジュールの特徴
- **第1週**: Cランク基礎固め
- **第2週**: シミュレーション/カウント集中（問題①/②対策）
- **第3週**: 探索/最小値問題集中（問題③対策）
- **第4週**: 模擬試験で実戦感覚を養う
- **最終週**: Bランク過去問/ランダム問題で仕上げ
""","""
そして次は3か月間の評価内容です。
- 学習スケジュールに沿って進められたか
- 各週ごとの学習目標を達成できたか

"""],

"Paiza Bランクに合格しました。":["""合格おめでとうございます！

それでは、PJTをご紹介いたします！

【プロジェクト名】

ガチャプロジェクト

【プロジェクト概要】

ユーザーがガチャ商品を登録し、掲示板に投稿してユーザー同士が交流でき、さらに地図を押すと自分の周囲のガチャショップを表示するプロジェクトです。"""],

"Paiza Bランクを取得してみます":["現在、ITに関する経験や知識はお持ちですか？"],

"いいえ。IT関連は初めてです。":["""はい！Paiza Bランクに合格し、その後IT企業に就職するために必要なカリキュラムをご提示します！
# Python学習カリキュラム（Paiza Bランク対策）

## 1段階. Python基礎文法（入門）

- **環境設定**: Pythonインストール、VS Code/IDLEの使い方
- **基本文法**
  - 変数、データ型(int, float, str, bool)
  - 入力(`input()`)、出力(`print()`)
  - 基本演算子(+ - * // % **)
- **条件分岐**
  - if / elif / else
  - 論理演算子(and, or, not)
- **繰り返し**
  - for, while
  - range() の活用
- **演習課題**
  - 合計を求める
  - 九九の出力
  - 星印パターンの出力

---

## 2段階. データ構造基礎（リスト・文字列中心）

- **リスト(list)**
  - 作成、インデックスアクセス、スライス
  - 追加/削除/ソート (`append`, `sort`, `remove`)
- **文字列(str)**
  - スライス、`split()`, `join()`, `replace()`
  - 文字列の走査(for … in …)
- **辞書(dict)、集合(set)**
  - key-value 構造
  - 重複除去
- **演習課題**
  - 文字列の逆順
  - リストの最大値/最小値を求める
  - 出現回数を数える（カウンター）

---

## 3段階. 関数 & 入出力処理

- **関数**
  - 定義/呼び出し (`def func():`)
  - 戻り値
- **標準入力処理**
  - `input().split()`
  - `map(int, input().split())`
- **複数行入力**
  - `for _ in range(N):` と組み合わせて使用
- **演習課題**
  - N個の数の合計/平均
  - 2次元リストを入力して出力

---

## 4段階. アルゴリズム基礎（Paiza C～Bランク必須）

- **シミュレーション**
  - 座標移動(F/B/L/R)、グリッド探索
  - 【問題①】チョコレート座標シミュレーション
- **カウント処理**
  - ビンゴ盤の行カウント (row, col, diagonal)
  - 【問題②】N×N ビンゴ
- **探索 & 最小値探索**
  - 全探索（Brute Force）
  - マンハッタン距離計算
  - 【問題③】映画館の座席
- **ソート & 条件処理**
  - `sorted()`, `key` 関数
- **演習課題**
  - 迷路移動
  - 数字カード探索
  - 最小距離探索

---

## 5段階. 問題解決の実戦練習

- **Paiza Cランク問題を全て解く**
  - 文字列処理、配列合計、条件分岐など
- **Bランク問題に挑戦**
  - 今回提示した3つの問題（チョコレート、ビンゴ、映画館の座席）と同様のタイプ
  - シミュレーション / カウント / 探索ベースの問題

[【Paizaのリンク】](https://paiza.jp/)

スケジュールとして整理しましょうか？
"""],

"3か月以内にできるスケジュールを作ってください。": ["""はい！3か月でできるPython学習スケジュールです。
# 📅 Python 12週間 日別学習スケジュール（Paiza Bランク対策）

## 🔹 第1～2週: Python基礎を固める
**目標**: Python文法と基本的な入出力に慣れる

- **Day 1**: Pythonインストール、VS Codeの使い方、`print()` 実習
- **Day 2**: 変数/データ型(int, float, str, bool)
- **Day 3**: 算術/比較/論理演算子 実習
- **Day 4**: if文、elif、else
- **Day 5**: ネストif、論理演算子応用
- **Day 6**: for文、range() 基本
- **Day 7**: while文、break/continue
- **Day 8**: `input()`, `split()`, `map()` 活用
- **Day 9**: 練習問題（九九、1～Nの合計）
- **Day 10**: 星印出力（△、□、ピラミッド）

---

## 🔹 第3～4週: データ構造 & 文字列
**目標**: リスト/文字列/辞書を自由に扱えるようにする

- **Day 11**: リスト基礎（作成、インデックス、スライス）
- **Day 12**: リスト追加/削除/ソート (`append`, `pop`, `sort`)
- **Day 13**: 文字列基礎、スライス
- **Day 14**: `split()`, `join()`
- **Day 15**: 文字列探索 (`in`, `find`, `count`)
- **Day 16**: 辞書(dict) 基礎、key-value
- **Day 17**: 集合(set) 基礎、重複除去
- **Day 18**: 文字列逆順 実習
- **Day 19**: 単語出現回数を数える（カウンタ実装）
- **Day 20**: リスト最大値/最小値 探索

---

## 🔹 第5～6週: 関数と2次元配列
**目標**: 関数定義と2次元データ処理に慣れる

- **Day 21**: 関数定義、呼び出し、引数
- **Day 22**: return、ローカル/グローバル変数
- **Day 23**: ネストリスト作成
- **Day 24**: 2次元リスト 入力/出力
- **Day 25**: 行列の加算 実習
- **Day 26**: 座標移動（上下左右シミュレーション）
- **Day 27**: 迷路入力して表示
- **Day 28**: 迷路探索（壁 `#`、道 `.`）実装
- **Day 29**: 総合演習（関数+2次元リスト応用）
- **Day 30**: 復習 + 間違い直し

---

## 🔹 第7～8週: アルゴリズム基礎
**目標**: Paiza Cランク問題を解ける力を身につける

- **Day 31**: シミュレーション基礎（状態更新）
- **Day 32**: カウント基礎（条件を満たす数を数える）
- **Day 33**: 全探索（Brute Force）
- **Day 34**: 最小/最大値探索
- **Day 35**: サイコロシミュレーション
- **Day 36**: 座標移動後の最終位置出力
- **Day 37**: マンハッタン距離計算 実習
- **Day 38**: 総合シミュレーション問題
- **Day 39**: カウント問題（数を数える、条件判定）
- **Day 40**: 復習 + Paiza Cランク問題3問

---

## 🔹 第9～10週: Paiza Cランクを全て解く
**目標**: 実戦感覚を鍛える

- **Day 41**: 文字列変換問題（大文字小文字変換、文字列圧縮）
- **Day 42**: リスト/配列の合計問題
- **Day 43**: 平均計算問題
- **Day 44**: 条件分岐問題（点数評価、合否判定）
- **Day 45**: 簡単なシミュレーション問題
- **Day 46**: Cランク問題3問解く
- **Day 47**: Cランク問題3問解く
- **Day 48**: Cランク問題3問解く
- **Day 49**: Cランク問題3問解く
- **Day 50**: 間違い直し + 復習

---

## 🔹 第11週: Paiza Bランク問題に集中
**目標**: 実際のBランク難易度問題を練習

- **Day 51**: 問題① チョコレート座標移動（シミュレーション）
- **Day 52**: 応用座標移動問題
- **Day 53**: 問題② N×N ビンゴ（カウント & ハッシュ）
- **Day 54**: ビンゴ応用問題（行完成チェック）
- **Day 55**: 問題③ 映画館の座席（最小距離探索）
- **Day 56**: マンハッタン距離応用問題
- **Day 57**: シミュレーション+カウント総合問題
- **Day 58**: Bランク問題1問挑戦
- **Day 59**: Bランク問題1問挑戦
- **Day 60**: 間違い直し + 復習

---

## 🔹 第12週: 実戦模擬試験 & 復習
**目標**: Bランク合格レベルに仕上げる

- **Day 61**: 模擬試験（1時間、問題1問）
- **Day 62**: 模擬試験（1時間、問題1問）
- **Day 63**: 模擬試験（1時間、問題1問）
- **Day 64**: 模擬試験（1時間、問題1問）
- **Day 65**: 模擬試験（1時間、問題1問）
- **Day 66**: 間違い直し
- **Day 67**: コード最適化練習（リファクタリング）
- **Day 68**: C/Bランク混合ランダム問題
- **Day 69**: Bランク追加練習
- **Day 70**: 最終チェック（入力処理、シミュレーション、時間/メモリ確認）
""","""そして次は3か月間の評価内容です。
- 学習スケジュールに沿って進められたか
- 各週ごとの学習目標を達成できたか"""]


}


# ✅ 답변 생성 함수 (공백/줄바꿈 무시)
async def generate_answer(question_text: str, mode: str = "chat"):
    if mode == "chat":
        scenario = QA_CHAT_SCENARIO
    elif mode == "log":
        scenario = QA_LOG_SCENARIO
    elif mode == "worker":
        scenario = QA_LOG_WORKER_SCENARIO
    elif mode == "worker-chat":
        scenario = QA_CHAT_WORKER_SCENARIO
    else:
        scenario = QA_CHAT_SCENARIO  # 기본값

    normalized_input = normalize_text(question_text)

    # 완전 일치
    for q, a in scenario.items():
        if normalized_input == normalize_text(q):
            return a

    # 부분 일치
    for q, a in scenario.items():
        if normalize_text(q) in normalized_input:
            return a

    return ["デモシナリオにない質問です。"]

def get_current_season_and_time():
    """[신규] 현재 JST 기준 시간, 계절, 시간대 인사를 반환합니다."""
    # JST (UTC+9)
    jst = datetime.timezone(datetime.timedelta(hours=9), 'JST')
    now = datetime.datetime.now(jst)

    month = now.month
    hour = now.hour

    if 3 <= month <= 5:
        season = "春"
    elif 6 <= month <= 8:
        season = "夏"
    elif 9 <= month <= 11:
        season = "秋"
    else:
        season = "冬"

    if 5 <= hour < 11:
        time_greeting = "朝"
    elif 11 <= hour < 17:
        time_greeting = "昼"
    else:
        time_greeting = "夜"

    return {
        "date": now.strftime('%Y年%m月%d日'),
        "time": now.strftime('%H:%M'),
        "season": season,
        "time_greeting": time_greeting
    }

async def get_current_weather():
    """[신규/수정] OpenWeatherMap API를 호출하여 실제 날씨를 가져옵니다."""

    # 1. .env 파일에서 API 키 읽어오기 (main.py에서 load_dotenv() 필요)
    api_key = os.environ.get("OPENWEATHER_API_KEY")

    # 💡 [설정] 날씨를 가져올 도시 이름 (예: 도쿄)
    city_name = "Sapporo"

    if not api_key:
        logger.warning("OPENWEATHER_API_KEY가 .env 파일에 없습니다. 더미 데이터를 반환합니다.")
        return "晴れ、気温12度" # (대체 응답)

    url = "https://api.openweathermap.org/data/2.5/weather"
    params = {
        "q": city_name,      # 도시 이름
        "appid": api_key,    # API 키
        "units": "metric",   # 섭씨(Celsius) 사용
        "lang": "ja"         # 언어: 일본어
    }

    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(url, params=params)
            response.raise_for_status() # 4xx, 5xx 에러 시 예외 발생

            data = response.json()
            description = data['weather'][0]['description']
            temp = round(data['main']['temp']) # 소수점 반올림

            formatted_weather = f"{description}、気温{temp}度" # 예: "快晴、気温12度"
            logger.info(f"날씨 API 호출 성공: {formatted_weather}")
            return formatted_weather

    except httpx.HTTPStatusError as e:
        logger.error(f"날씨 API 호출 실패 (HTTP Status): {e}")
        return "天気情報が取得できませんでした"
    except Exception as e:
        logger.error(f"날씨 API 처리 중 알 수 없는 오류: {e}")
        return "天気情報が取得できませんでした"

# ✅ 질문 API
@router.post("/demo/api/ask_question")
async def gemini_question(user_question: UserQuestion, background_tasks: BackgroundTasks):
    question_text = user_question.question.strip()
    mode = getattr(user_question, "mode", "chat")  # 기본은 "chat"

    # 응답 생성
    answer = await generate_answer(question_text, mode=mode)

    return {"answer": answer}


@router.post("/demo/api/request-signal/{signal_no}")
async def request_signal(signal_no: int):
    """
    [역할] 지정된 signal_no의 is_requested를 true로 변경합니다. (신호 요청)
    """
    try:
        query = "UPDATE signals SET is_requested = TRUE WHERE signal_no = :signal_no"
        await database_demo.execute(query, values={"signal_no": signal_no})

        # ❌ 성공 메시지는 로그에 남기지 않도록 주석 처리
        # print(f"✅ 신호 요청: Signal #{signal_no}의 상태를 TRUE로 변경 요청했습니다.")
        return {"status": "ok", "detail": f"Signal {signal_no} has been requested."}

    except Exception as e:
        # 🆘 에러 발생 시에만 로그를 기록합니다.
        logger.error(f"Error in request_signal for signal_no {signal_no}: {e}")
        raise HTTPException(status_code=500, detail=f"An error occurred: {e}")


@router.get("/demo/api/check-signal")
async def check_signal():
    """
    [역할] is_requested가 true인 신호를 찾아 메시지를 반환하고,
           즉시 해당 신호의 is_requested를 false로 되돌립니다.
    """
    try:
        async with database_demo.transaction():
            query_select = "SELECT signal_no, message FROM signals WHERE is_requested = TRUE LIMIT 1"
            result = await database_demo.fetch_one(query_select)

            if result:
                signal_to_process = dict(result)

                # ❌ 성공 메시지는 로그에 남기지 않도록 주석 처리
                # print(f"📬 신호 발견: Signal #{signal_to_process['signal_no']} 처리 시작.")

                query_update = "UPDATE signals SET is_requested = FALSE WHERE signal_no = :signal_no"
                await database_demo.execute(query_update, values={"signal_no": signal_to_process['signal_no']})

                # ❌ 성공 메시지는 로그에 남기지 않도록 주석 처리
                # print(f"✔️ 처리 완료: Signal #{signal_to_process['signal_no']}의 상태를 FALSE로 되돌렸습니다.")
                return {"has_signal": True, "data": signal_to_process}
            else:
                return {"has_signal": False, "data": None}

    except Exception as e:
        # 🆘 에러 발생 시에만 로그를 기록합니다.
        logger.error(f"Error in check_signal: {e}")
        raise HTTPException(status_code=500, detail=f"An error occurred: {e}")



@router.post("/demo/api/submit-advice")
async def submit_advice(payload: dict):
    """
    [역할] 프론트에서 받은 Advice 텍스트를 advice_queue 테이블에 저장합니다.
    (is_processed의 기본값은 FALSE이므로 별도 지정 필요 없음)
    """
    advice_text = payload.get("advice")
    if not advice_text:
        raise HTTPException(status_code=400, detail="Advice text is required")

    try:
        query = "INSERT INTO advice_queue (advice_text) VALUES (:advice_text)"
        await database_demo.execute(query, values={"advice_text": advice_text})
        return {"status": "ok", "detail": "Advice has been submitted."}

    except Exception as e:
        logger.error(f"Error in submit_advice: {e}")
        raise HTTPException(status_code=500, detail=f"An error occurred: {e}")


@router.get("/demo/api/check-advice")
async def check_advice():
    """
    [역할 수정] is_processed이 FALSE인 '처리되지 않은' Advice를 찾습니다.
    찾으면 해당 Advice의 상태를 TRUE('처리 중')로 바꾸고 내용을 반환합니다.
    """
    try:
        async with database_demo.transaction():
            # 1. is_processed이 FALSE인 가장 오래된 메시지를 찾습니다.
            query_select = "SELECT id, advice_text FROM advice_queue WHERE is_processed = FALSE ORDER BY id ASC LIMIT 1 FOR UPDATE"
            result = await database_demo.fetch_one(query_select)

            if result:
                advice_to_process = dict(result)
                advice_id = advice_to_process['id']

                # 2. 찾은 메시지의 상태를 is_processed = TRUE 로 변경하여 다른 요청이 가져가지 못하게 '잠금'합니다.
                query_update = "UPDATE advice_queue SET is_processed = TRUE WHERE id = :id"
                await database_demo.execute(query_update, values={"id": advice_id})

                return {"has_advice": True, "advice": advice_to_process}
            else:
                return {"has_advice": False, "advice": None}

    except Exception as e:
        logger.error(f"Error in check_advice: {e}")
        raise HTTPException(status_code=500, detail=f"An error occurred: {e}")


@router.post("/demo/api/confirm-advice/{advice_id}")
async def confirm_advice(advice_id: int):
    """
    [수정된 기능] 프론트엔드에서 메시지 표시가 완료되었음을 확인받고,
    해당 Advice의 is_processed를 TRUE로 변경하여 '처리 완료' 상태로 만듭니다.
    """
    try:
        # ✅ 'is_processed' 컬럼을 TRUE로 업데이트하는 쿼리
        query = "UPDATE advice_queue SET is_processed = TRUE WHERE id = :id"
        await database_demo.execute(query, values={"id": advice_id})
        return {"status": "ok", "detail": f"Advice #{advice_id} has been marked as processed."}

    except Exception as e:
        logger.error(f"Error in confirm_advice for ID {advice_id}: {e}")
        raise HTTPException(status_code=500, detail=f"An error occurred: {e}")


# -----------------------------------------------------------------
# 💡 [신규] 라즈베리파이용 STT (파일 업로드) 엔드포인트
# -----------------------------------------------------------------
@router.post("/demo/api/stt-from-file")
async def process_stt_from_file(file: UploadFile = File(...)):
    """
    [로봇용 STT 엔드포인트]
    1. Pi로부터 'audio/wav' 파일을 업로드 받습니다.
    2. OpenAI Whisper-1 모델을 사용하여 텍스트로 변환합니다.
    3. 변환된 텍스트(transcript)를 Pi에게 JSON으로 반환합니다.
    """
    try:
        if not file:
            raise HTTPException(status_code=400, detail="오디오 파일이 없습니다.")

        logger.info(f"STT 파일 수신: {file.filename}, type={file.content_type}")

        # UploadFile의 file-like 객체(file.file)를 Whisper API로 전달
        transcription_response = client.audio.transcriptions.create(
            model="whisper-1",
            file=(file.filename, file.file, file.content_type),
            language="ja"
        )

        transcribed_text = transcription_response.text
        logger.info(f"Whisper STT 변환 결과: {transcribed_text}")

        return {
            "status": "success",
            "text": transcribed_text
        }

    except Exception as e:
        logger.error(f"'/demo/api/stt-from-file' 처리 중 오류: {e}")
        raise HTTPException(status_code=500, detail=f"STT 변환 실패: {e}")


# -----------------------------------------------------------------
# 💡 [추가] 라즈베리파이 로봇용 신규 엔드포인트
# -----------------------------------------------------------------
# 💡 [중요] Pi가 'name.mp3' 재생 후 이 AI의 응답을 DB에 저장해야 함
FIRST_AI_PROMPT = "良い一日をお過ごしください。お名前は何ですか？" # Pi의 name.mp3 내용과 일치해야 함

@router.post("/demo/api/process-text") # 💡 Postman 경로와 일치
async def process_robot_multi_turn(request: RobotRequest):
    """
    [로봇용 멀티턴(연속대화) 엔드포인트]
    1. Pi로부터 'user_input'과 'session_id'(선택)를 받습니다.
    2. session_id가 없으면: 새 ID를 만들고, 첫 AI 질문('이름이뭐야')을 DB에 저장합니다.
    3. 사용자의 'user_input'을 DB에 저장합니다.
    4. 해당 'session_id'의 모든 대화 이력을 DB에서 가져옵니다.
    5. 대화 이력 + 날씨/시간 컨텍스트로 GPT를 호출합니다.
    6. GPT의 응답을 DB에 저장합니다.
    7. GPT의 응답을 TTS로 변환(S3 업로드)합니다.
    8. Pi에게 'audio_url'과 'session_id'를 반환합니다.
    """
    user_text = request.user_input
    session_id = request.session_id
    is_last_turn = request.is_last_turn

    logger.info(f"로봇 멀티턴 요청 수신: session_id={session_id}, input={user_text}")

    try:
        # 1. 세션 ID가 없는 경우 (첫 번째 턴)
        if not session_id:
            session_id = f"robot-{uuid.uuid4()}" # 새 세션 ID 생성
            logger.info(f"새 세션 생성: {session_id}")
            # 💡 [중요] Pi가 방금 재생한 'name.mp3'의 내용을 AI의 첫 발화로 DB에 저장
            await db_module.save_robot_chat_message(session_id, 'assistant', FIRST_AI_PROMPT)

        # 2. 사용자의 현재 발화(user_text)를 DB에 저장
        await db_module.save_robot_chat_message(session_id, 'user', user_text)

        # 3. DB에서 이 세션의 '모든' 대화 이력 가져오기
        history_rows = await db_module.get_robot_chat_history(session_id)
        # 💡 DB 결과(Row)를 GPT가 알아듣는 딕셔너리 리스트로 변환
        history_messages = [{"role": row["role"], "content": row["content"]} for row in history_rows]

        # 4. 실시간 컨텍스트(날씨, 시간) 가져오기
        context = get_current_season_and_time()
        weather = await get_current_weather()

        # 5. 시스템 프롬프트 완성하기
        system_prompt = f"""
# あなたの役割
あなたは、キャンパスに展示されている、親しみやすく知的な対話型ロボットです。
あなたの主な目的は、学生、教職員、訪問者と自然で、役に立ち、時宜にかなった（その時々に合った）会話を行うことです。

# 会話のルール
1. 常に礼儀正しく、親しみやすいトーンで応答してください。
2. 質問に対して、単に情報で答えるだけでなく、会話を広げるような（キャッチボールするような）応答を心がけてください。
3. 以下の「現在のコンテキスト」情報を、会話の中に**自然に**織り込んでください。

# 現在のコンテキスト
* 現在の日付: {context['date']}
* 現在の時刻: {context['time']} ({context['time_greeting']})
* 現在の季節: {context['season']}
* 今日の天気: {weather}
* あなたの場所: 大学 エントランス
"""

        if is_last_turn:
                    system_prompt += """
        # 最終応答の指示
        会話の最後のターンです。相手の言葉に共感・同意した後、応答の最後は**必ず「お元気でね」**というフレーズで締めくくってください。
        ※重要: 「またね」や「さようなら」などに言い換えず、一字一句正確に「お元気でね」を使用してください。
        """

        # 💡 [중요] GPT에게 프롬프트 + '전체 대화 이력'을 함께 전달
        messages_for_gpt = [{"role": "system", "content": system_prompt}] + history_messages

        # 6. OpenAI ChatCompletion API 호출 (동기식)
        chat_response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages_for_gpt, # 💡 수정
            max_tokens=150
        )
        gpt_response_text = chat_response.choices[0].message.content
        logger.info(f"GPT 자연 대화 응답: {gpt_response_text}")

        # 7. GPT의 응답(ai)을 DB에 저장
        await db_module.save_robot_chat_message(session_id, 'assistant', gpt_response_text)

        # 8. S3 TTS 함수 호출 (동기식)
        s3_audio_url = await synthesize_speech(gpt_response_text, session_id)

        if not s3_audio_url:
            raise HTTPException(status_code=500, detail="TTS S3 업로드에 실패했습니다.")

        # 9. Pi에게 audio_url과 session_id 반환
        return {
            "audio_url": s3_audio_url,
            "session_id": session_id
        }

    except Exception as e:
        logger.error(f"'/demo/api/process-text' (멀티턴) 처리 중 오류: {e}")
        raise HTTPException(status_code=500, detail=f"서버 내부 오류: {e}")
