# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Voice / Transcription Skill

Lightweight audio transcription, voice notes, and dictation.
Focuses on file-based workflows complementing the full voice skill.

Backends:
  1. faster-whisper (local, fast)
  2. openai-whisper (local, original)
  3. OpenAI Whisper API (cloud)

Dependencies (all optional, at least one needed for transcription):
  - faster-whisper
  - openai-whisper
  - openai (API client)
"""

import json
import logging
import os
import subprocess
from datetime import datetime
from pathlib import Path


def _get_data_dir():
    """Get data directory (tenant-scoped in Reflection, default in standalone)."""
    try:
        from familiar.core import paths

        return paths.DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


try:
    from familiar.core.utils import atomic_write_json, generate_id, safe_load_json
except ImportError:
    atomic_write_json = None
    safe_load_json = None
    generate_id = None

logger = logging.getLogger(__name__)


def _get_data_file():
    """Get data file path (re-evaluates for tenant scoping)."""
    return _get_data_dir() / "transcriptions.json"


DATA_FILE = _get_data_file()  # Default for backward compat
AUDIO_DIR = Path.home() / ".familiar" / "audio"

_DEFAULT_DATA = {"transcriptions": [], "version": 1}

# Probe backends
FASTER_WHISPER = False
WHISPER = False
OPENAI_API = False

try:
    from faster_whisper import WhisperModel  # noqa: F401

    FASTER_WHISPER = True
except ImportError as e:
    logger.debug("Optional dependency not available: %s", e)
if not FASTER_WHISPER:
    try:
        import whisper

        WHISPER = True
    except ImportError as e:
        logger.debug("Optional dependency not available: %s", e)
if not FASTER_WHISPER and not WHISPER:
    try:
        import openai

        OPENAI_API = bool(os.environ.get("OPENAI_API_KEY"))
    except ImportError as e:
        logger.debug("Optional dependency not available: %s", e)
def _load_data():
    if safe_load_json:
        return safe_load_json(
            _get_data_file(), default=lambda: json.loads(json.dumps(_DEFAULT_DATA))
        )
    if _get_data_file().exists():
        try:
            with open(_get_data_file(), encoding="utf-8") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
    return json.loads(json.dumps(_DEFAULT_DATA))


def _save_data(data):
    if atomic_write_json:
        atomic_write_json(_get_data_file(), data)
    else:
        _get_data_file().parent.mkdir(parents=True, exist_ok=True)
        with open(_get_data_file(), "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)


def _gen_id():
    if generate_id:
        return generate_id()
    import secrets

    return secrets.token_hex(4)


def _get_backend():
    """Return the best available transcription backend name."""
    if FASTER_WHISPER:
        return "faster-whisper"
    if WHISPER:
        return "openai-whisper"
    if OPENAI_API:
        return "openai-api"
    return None


def _get_audio_duration(filepath):
    """Get audio duration using ffprobe if available."""
    try:
        result = subprocess.run(
            [
                "ffprobe",
                "-v",
                "quiet",
                "-show_entries",
                "format=duration",
                "-of",
                "default=noprint_wrappers=1:nokey=1",
                str(filepath),
            ],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return float(result.stdout.strip())
    except Exception:
        return None


def _transcribe_file(filepath, language="en", model_name=None):
    """Transcribe audio file using best available backend."""
    path = Path(filepath)
    if not path.exists():
        return None, f"File not found: {filepath}"

    backend = _get_backend()
    if not backend:
        # Auto-install faster-whisper (recommended, local)
        from familiar.core.deps import WHISPER_PACKAGES, ensure_packages

        ok, _ = ensure_packages(WHISPER_PACKAGES)
        if ok:
            global FASTER_WHISPER, WhisperModel
            from faster_whisper import WhisperModel as _WM

            WhisperModel = _WM
            FASTER_WHISPER = True
            backend = "faster-whisper"
        else:
            return (
                None,
                "No transcription backend available — faster-whisper could not be installed.",
            )

    try:
        if backend == "faster-whisper":
            model = WhisperModel(model_name or "base", compute_type="int8")
            segments, info = model.transcribe(str(path), language=language)
            text = " ".join(seg.text.strip() for seg in segments)
            return text, f"faster-whisper ({model_name or 'base'})"

        elif backend == "openai-whisper":
            model = whisper.load_model(model_name or "base")
            result = whisper.transcribe(model, str(path), language=language)
            return result["text"], f"openai-whisper ({model_name or 'base'})"

        elif backend == "openai-api":
            # Uses `openai` imported at module level under OPENAI_API guard
            client = openai.OpenAI()
            with open(path, "rb") as f:
                response = client.audio.transcriptions.create(
                    model="whisper-1", file=f, language=language
                )
            return response.text, "openai-api (whisper-1)"

    except Exception as e:
        return None, f"Transcription error ({backend}): {e}"

    return None, "Unknown error"


# === Tool Handlers ===


def transcribe_audio(data):
    """Transcribe an audio file to text."""
    filepath = data.get("filepath", "").strip()
    if not filepath:
        return "Please provide an audio file path."

    language = data.get("language", "en")
    model = data.get("model")
    save = data.get("save", True)

    text, backend_info = _transcribe_file(filepath, language, model)
    if text is None:
        return f"❌ Transcription failed: {backend_info}"

    duration = _get_audio_duration(filepath)
    duration_str = f"{duration:.1f}s" if duration else "unknown"

    # Save to history
    if save:
        db = _load_data()
        entry = {
            "id": _gen_id(),
            "filepath": filepath,
            "text": text,
            "language": language,
            "backend": backend_info,
            "duration_seconds": duration,
            "word_count": len(text.split()),
            "created_at": datetime.now().isoformat(),
        }
        db["transcriptions"].append(entry)
        if len(db["transcriptions"]) > 500:
            db["transcriptions"] = db["transcriptions"][-500:]
        _save_data(db)

    lines = [
        f"🎤 Transcription ({backend_info})",
        f"   File: {Path(filepath).name} ({duration_str})",
        f"   Words: {len(text.split())}",
        "",
        text,
    ]
    return "\n".join(lines)


def voice_note(data):
    """Transcribe audio and save as knowledge base entry."""
    filepath = data.get("filepath", "").strip()
    if not filepath:
        return "Please provide an audio file path."

    tags = data.get("tags", ["voice_note"])
    if isinstance(tags, str):
        tags = [t.strip() for t in tags.split(",") if t.strip()]
    title = data.get("title", "").strip()

    # Transcribe
    text, backend_info = _transcribe_file(filepath, data.get("language", "en"))
    if text is None:
        return f"❌ Transcription failed: {backend_info}"

    if not title:
        title = f"Voice Note {datetime.now().strftime('%Y-%m-%d %H:%M')}"

    # Save transcription history
    db = _load_data()
    entry = {
        "id": _gen_id(),
        "filepath": filepath,
        "text": text,
        "backend": backend_info,
        "type": "voice_note",
        "created_at": datetime.now().isoformat(),
    }
    db["transcriptions"].append(entry)
    _save_data(db)

    # Save to knowledge base
    try:
        import importlib.util
        import sys

        kb_path = Path(__file__).parent.parent / "knowledge_base" / "skill.py"
        spec = importlib.util.spec_from_file_location("kb_vn", str(kb_path))
        module = importlib.util.module_from_spec(spec)
        if "kb_vn" not in sys.modules:
            sys.modules["kb_vn"] = module
            spec.loader.exec_module(module)
        else:
            module = sys.modules["kb_vn"]
        kb_result = module.save_to_knowledge(
            {
                "title": title,
                "content": text,
                "tags": tags,
                "source": filepath,
                "type": "meeting_note",
            }
        )
        return f"🎤 Voice note saved ({backend_info}):\n   {title}\n   {len(text.split())} words\n   {kb_result}"
    except Exception as e:
        return f"🎤 Transcribed ({backend_info}), but knowledge base save failed: {e}\n\n{text}"


def dictation_to_text(data):
    """Transcribe audio and return clean text for editing/use."""
    filepath = data.get("filepath", "").strip()
    if not filepath:
        return "Please provide an audio file path."

    text, backend_info = _transcribe_file(filepath, data.get("language", "en"))
    if text is None:
        return f"❌ Transcription failed: {backend_info}"

    # Clean up for dictation
    clean = text.strip()
    # Basic punctuation normalization
    clean = clean.replace("  ", " ")

    # Save to history
    db = _load_data()
    db["transcriptions"].append(
        {
            "id": _gen_id(),
            "filepath": filepath,
            "text": clean,
            "type": "dictation",
            "backend": backend_info,
            "created_at": datetime.now().isoformat(),
        }
    )
    _save_data(db)

    return clean


def transcription_history(data):
    """View past transcriptions with search."""
    db = _load_data()
    entries = db.get("transcriptions", [])
    query = data.get("query", "").strip().lower()
    limit = min(data.get("limit", 15), 100)

    if query:
        entries = [
            e
            for e in entries
            if query in e.get("text", "").lower() or query in e.get("filepath", "").lower()
        ]

    entries = entries[-limit:]
    entries.reverse()

    if not entries:
        return "No transcription history found."

    backend = _get_backend() or "none available"

    lines = [f"🎤 Transcription History ({len(entries)} shown, backend: {backend}):\n"]
    for e in entries:
        dt = e.get("created_at", "")[:16].replace("T", " ")
        fname = Path(e.get("filepath", "")).name if e.get("filepath") else "unknown"
        words = len(e.get("text", "").split())
        etype = e.get("type", "transcription")
        preview = e.get("text", "")[:60].replace("\n", " ")
        lines.append(f"  [{e.get('id', '')}] {dt} {fname} ({words} words, {etype})")
        lines.append(f"     {preview}...")

    return "\n".join(lines)


# === Tool Definitions ===

TOOLS = [
    {
        "name": "transcribe_audio",
        "description": (
            "Quick audio-to-text transcription with automatic history saving. "
            "Supports any audio format via faster-whisper, openai-whisper, or the OpenAI API. "
            "For advanced options like translation, word timestamps, or speaker diarization, use the transcribe tool instead. "
            "Returns transcribed text with word count and duration."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "filepath": {"type": "string", "description": "Path to audio file"},
                "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Language code (en, es, fr, etc.)",
                },
                "model": {
                    "type": "string",
                    "description": "Whisper model size (tiny, base, small, medium, large)",
                },
                "save": {
                    "type": "boolean",
                    "default": True,
                    "description": "Save to transcription history",
                },
            },
            "required": ["filepath"],
        },
        "handler": transcribe_audio,
        "category": "transcription",
    },
    {
        "name": "voice_note",
        "description": (
            "Transcribe an audio file and automatically save the result as a knowledge base entry. "
            "The transcription is stored in both the transcription history and the knowledge base with configurable tags. "
            "Use this for meeting recordings or voice memos that you want to search later. "
            "Defaults to the 'voice_note' tag if none are provided."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "filepath": {"type": "string", "description": "Path to audio file"},
                "title": {
                    "type": "string",
                    "description": "Note title (auto-generated if omitted)",
                },
                "tags": {
                    "type": "array",
                    "items": {
                        "type": "string",
                        "description": "Tag name for organizing the voice note",
                    },
                    "default": ["voice_note"],
                    "description": "Tags for organizing the knowledge base entry",
                },
                "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Language code (en, es, fr, etc.)",
                },
            },
            "required": ["filepath"],
        },
        "handler": voice_note,
        "category": "transcription",
    },
    {
        "name": "dictation_to_text",
        "description": (
            "Transcribe an audio file and return clean, normalized text suitable for editing or direct use. "
            "Unlike voice_note, this does not save to the knowledge base -- it only returns the text. "
            "The transcription is saved to history for later reference. "
            "Use this for dictation workflows where you need the raw text output."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "filepath": {"type": "string", "description": "Path to audio file"},
                "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Language code (en, es, fr, etc.)",
                },
            },
            "required": ["filepath"],
        },
        "handler": dictation_to_text,
        "category": "transcription",
    },
    {
        "name": "transcription_history",
        "description": (
            "View past transcriptions with optional text search across transcription content and file names. "
            "Shows the most recent entries first with word counts and backend info. "
            "Use this to find a previous transcription or review recent voice activity."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search transcription text"},
                "limit": {
                    "type": "integer",
                    "default": 15,
                    "description": "Maximum number of results to return",
                },
            },
        },
        "handler": transcription_history,
        "category": "transcription",
    },
]
