"""
API v1 - RESTful API for external integrations.

This blueprint provides a comprehensive REST API for:
- Dashboard widgets (gethomepage.dev, etc.)
- Automation tools (n8n, Zapier, etc.)
- Third-party integrations

All endpoints require token authentication via:
- Authorization: Bearer <token>
- X-API-Token: <token>
- API-Token: <token>
- ?token=<token> query parameter
"""

import os
import re
import json
from datetime import datetime, date, timedelta
from typing import Optional

from flask import Blueprint, jsonify, request, current_app, send_file
from flask_login import login_required, current_user
from sqlalchemy import func, extract, or_, and_

from src.database import db
from src.models import Recording, User, Tag, RecordingTag, Speaker, Event
from src.models.processing_job import ProcessingJob
from src.models.token_usage import TokenUsage
from src.models.transcription_usage import TranscriptionUsage
from src.services.token_tracking import TokenTracker
from src.services.transcription_tracking import transcription_tracker
from src.file_exporter import format_transcription_with_template
from src.api.recordings import upload_file as _upload_file_ui

# Create blueprint with /api/v1 prefix
api_v1_bp = Blueprint('api_v1', __name__, url_prefix='/api/v1')

# Global helpers (will be injected from app)
has_recording_access = None
get_user_recording_status = None
set_user_recording_status = None
enrich_recording_dict_with_user_status = None
bcrypt = None
csrf = None
limiter = None
chunking_service = None

# Token tracker instance
token_tracker = TokenTracker()


def init_api_v1_helpers(**kwargs):
    """Initialize helper functions and extensions from app."""
    global has_recording_access, get_user_recording_status, set_user_recording_status
    global enrich_recording_dict_with_user_status, bcrypt, csrf, limiter, chunking_service
    has_recording_access = kwargs.get('has_recording_access')
    get_user_recording_status = kwargs.get('get_user_recording_status')
    set_user_recording_status = kwargs.get('set_user_recording_status')
    enrich_recording_dict_with_user_status = kwargs.get('enrich_recording_dict_with_user_status')
    bcrypt = kwargs.get('bcrypt')
    csrf = kwargs.get('csrf')
    limiter = kwargs.get('limiter')
    chunking_service = kwargs.get('chunking_service')


def format_bytes(bytes_value: int) -> str:
    """Format bytes to human-readable string."""
    if bytes_value is None:
        bytes_value = 0
    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
        if bytes_value < 1024:
            return f"{bytes_value:.1f} {unit}"
        bytes_value /= 1024
    return f"{bytes_value:.1f} PB"


# =============================================================================
# OpenAPI Documentation
# =============================================================================

OPENAPI_SPEC = {
    "openapi": "3.0.3",
    "info": {
        "title": "Speakr API",
        "description": "REST API for Speakr - Audio transcription and note-taking application.\n\n## Authentication\nAll endpoints require token authentication via one of:\n- `Authorization: Bearer <token>`\n- `X-API-Token: <token>`\n- `API-Token: <token>`\n- `?token=<token>` query parameter\n\nGenerate tokens in Settings > API Tokens.",
        "version": "1.0.0"
    },
    "servers": [{"url": "/api/v1", "description": "API v1"}],
    "components": {
        "securitySchemes": {
            "bearerAuth": {"type": "http", "scheme": "bearer"},
            "apiKeyHeader": {"type": "apiKey", "in": "header", "name": "X-API-Token"},
            "apiKeyQuery": {"type": "apiKey", "in": "query", "name": "token"}
        },
        "schemas": {
            "Recording": {
                "type": "object",
                "properties": {
                    "id": {"type": "integer"},
                    "title": {"type": "string"},
                    "status": {"type": "string", "enum": ["PENDING", "PROCESSING", "SUMMARIZING", "COMPLETED", "FAILED"]},
                    "created_at": {"type": "string", "format": "date-time"},
                    "meeting_date": {"type": "string", "format": "date-time"},
                    "file_size": {"type": "integer"},
                    "participants": {"type": "string"},
                    "is_inbox": {"type": "boolean"},
                    "is_highlighted": {"type": "boolean"},
                    "tags": {"type": "array", "items": {"$ref": "#/components/schemas/Tag"}}
                }
            },
            "Tag": {
                "type": "object",
                "properties": {
                    "id": {"type": "integer"},
                    "name": {"type": "string"},
                    "color": {"type": "string"},
                    "custom_prompt": {"type": "string"},
                    "default_language": {"type": "string"},
                    "default_min_speakers": {"type": "integer"},
                    "default_max_speakers": {"type": "integer"}
                }
            },
            "Speaker": {
                "type": "object",
                "properties": {
                    "id": {"type": "integer"},
                    "name": {"type": "string"},
                    "use_count": {"type": "integer"},
                    "has_voice_profile": {"type": "boolean"}
                }
            },
            "Error": {
                "type": "object",
                "properties": {"error": {"type": "string"}}
            }
        }
    },
    "security": [{"bearerAuth": []}, {"apiKeyHeader": []}, {"apiKeyQuery": []}],
    "paths": {
        "/stats": {
            "get": {
                "tags": ["Stats"],
                "summary": "Get system statistics",
                "description": "Returns stats compatible with gethomepage.dev widgets",
                "parameters": [{"name": "scope", "in": "query", "schema": {"type": "string", "enum": ["user", "all"], "default": "user"}, "description": "user=personal stats, all=global (admin only)"}],
                "responses": {"200": {"description": "Stats object"}}
            }
        },
        "/recordings": {
            "get": {
                "tags": ["Recordings"],
                "summary": "List recordings",
                "parameters": [
                    {"name": "page", "in": "query", "schema": {"type": "integer", "default": 1}},
                    {"name": "per_page", "in": "query", "schema": {"type": "integer", "default": 25, "maximum": 100}},
                    {"name": "status", "in": "query", "schema": {"type": "string", "enum": ["all", "pending", "processing", "completed", "failed"]}},
                    {"name": "sort_by", "in": "query", "schema": {"type": "string", "enum": ["created_at", "meeting_date", "title", "file_size"]}},
                    {"name": "sort_order", "in": "query", "schema": {"type": "string", "enum": ["asc", "desc"]}},
                    {"name": "tag_id", "in": "query", "schema": {"type": "integer"}},
                    {"name": "q", "in": "query", "schema": {"type": "string"}, "description": "Search query"}
                ],
                "responses": {"200": {"description": "Paginated list of recordings"}}
            }
        },
        "/recordings/{id}": {
            "get": {
                "tags": ["Recordings"],
                "summary": "Get recording details",
                "parameters": [
                    {"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}},
                    {"name": "format", "in": "query", "schema": {"type": "string", "enum": ["full", "minimal"]}, "description": "minimal excludes large text fields"},
                    {"name": "include", "in": "query", "schema": {"type": "string"}, "description": "Comma-separated: transcription,summary,notes"}
                ],
                "responses": {"200": {"description": "Recording details"}, "404": {"description": "Not found"}}
            },
            "patch": {
                "tags": ["Recordings"],
                "summary": "Update recording",
                "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
                "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"title": {"type": "string"}, "participants": {"type": "string"}, "notes": {"type": "string"}, "summary": {"type": "string"}, "meeting_date": {"type": "string"}, "is_inbox": {"type": "boolean"}, "is_highlighted": {"type": "boolean"}}}}}},
                "responses": {"200": {"description": "Updated recording"}}
            },
            "delete": {
                "tags": ["Recordings"],
                "summary": "Delete recording",
                "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
                "responses": {"200": {"description": "Deleted"}, "403": {"description": "Permission denied"}}
            }
        },
        "/recordings/{id}/transcript": {
            "get": {
                "tags": ["Recordings"],
                "summary": "Get transcript",
                "parameters": [
                    {"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}},
                    {"name": "format", "in": "query", "schema": {"type": "string", "enum": ["json", "text", "srt", "vtt"], "default": "json"}}
                ],
                "responses": {"200": {"description": "Transcript in requested format"}}
            }
        },
        "/recordings/{id}/summary": {
            "get": {"tags": ["Recordings"], "summary": "Get summary", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Summary markdown"}}},
            "put": {"tags": ["Recordings"], "summary": "Replace summary", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["summary"], "properties": {"summary": {"type": "string"}}}}}}, "responses": {"200": {"description": "Updated"}}}
        },
        "/recordings/{id}/notes": {
            "get": {"tags": ["Recordings"], "summary": "Get notes", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Notes markdown"}}},
            "put": {"tags": ["Recordings"], "summary": "Replace notes", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["notes"], "properties": {"notes": {"type": "string"}}}}}}, "responses": {"200": {"description": "Updated"}}}
        },
        "/recordings/{id}/status": {
            "get": {"tags": ["Recordings"], "summary": "Get processing status", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Status with queue position"}}}
        },
        "/recordings/{id}/transcribe": {
            "post": {"tags": ["Processing"], "summary": "Queue transcription", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"language": {"type": "string"}, "min_speakers": {"type": "integer"}, "max_speakers": {"type": "integer"}}}}}}, "responses": {"200": {"description": "Job queued"}}}
        },
        "/recordings/{id}/summarize": {
            "post": {"tags": ["Processing"], "summary": "Queue summarization", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"custom_prompt": {"type": "string"}}}}}}, "responses": {"200": {"description": "Job queued"}}}
        },
        "/recordings/{id}/chat": {
            "post": {"tags": ["Chat"], "summary": "Chat about recording", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["message"], "properties": {"message": {"type": "string"}, "conversation_history": {"type": "array"}}}}}}, "responses": {"200": {"description": "Chat response"}}}
        },
        "/recordings/{id}/events": {
            "get": {"tags": ["Events"], "summary": "Get calendar events", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "List of events"}}}
        },
        "/recordings/{id}/events/ics": {
            "get": {"tags": ["Events"], "summary": "Download events as ICS", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "ICS file", "content": {"text/calendar": {}}}}}
        },
        "/recordings/{id}/audio": {
            "get": {"tags": ["Audio"], "summary": "Download audio", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}, {"name": "download", "in": "query", "schema": {"type": "boolean"}}], "responses": {"200": {"description": "Audio file"}}}
        },
        "/recordings/{id}/tags": {
            "post": {"tags": ["Tags"], "summary": "Add tags to recording", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"tag_ids": {"type": "array", "items": {"type": "integer"}}}}}}}, "responses": {"200": {"description": "Tags added"}}}
        },
        "/recordings/{id}/tags/{tag_id}": {
            "delete": {"tags": ["Tags"], "summary": "Remove tag from recording", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}, {"name": "tag_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Tag removed"}}}
        },
        "/recordings/{id}/speakers": {
            "get": {"tags": ["Speakers"], "summary": "Get speakers in recording", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Speakers with suggestions"}}}
        },
        "/recordings/{id}/speakers/assign": {
            "put": {
                "tags": ["Speakers"],
                "summary": "Assign speaker names to transcription",
                "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
                "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["speaker_map"], "properties": {"speaker_map": {"type": "object", "description": "Map of speaker labels to names. Values can be: string (name) or object {name, isMe}."}, "regenerate_summary": {"type": "boolean", "default": False}}}}}},
                "responses": {"200": {"description": "Speakers assigned"}, "404": {"description": "Recording not found"}, "403": {"description": "Permission denied"}}
            }
        },
        "/recordings/{id}/speakers/identify": {
            "post": {
                "tags": ["Speakers"],
                "summary": "Auto-identify speakers via LLM",
                "description": "Analyzes transcript context to suggest speaker names. Returns suggestions only - does not modify the recording.",
                "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
                "responses": {"200": {"description": "Speaker identification suggestions"}, "400": {"description": "Transcription not available or unsupported format"}}
            }
        },
        "/recordings/batch": {
            "patch": {"tags": ["Batch"], "summary": "Batch update recordings", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["recording_ids", "updates"], "properties": {"recording_ids": {"type": "array", "items": {"type": "integer"}}, "updates": {"type": "object"}}}}}}, "responses": {"200": {"description": "Batch results"}}},
            "delete": {"tags": ["Batch"], "summary": "Batch delete recordings", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["recording_ids"], "properties": {"recording_ids": {"type": "array", "items": {"type": "integer"}}}}}}}, "responses": {"200": {"description": "Batch results"}}}
        },
        "/recordings/batch/transcribe": {
            "post": {"tags": ["Batch"], "summary": "Batch queue transcriptions", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["recording_ids"], "properties": {"recording_ids": {"type": "array", "items": {"type": "integer"}}}}}}}, "responses": {"200": {"description": "Batch results"}}}
        },
        "/recordings/upload": {
            "post": {
                "tags": ["Recordings"],
                "summary": "Upload a recording (multipart form-data) and queue transcription",
                "requestBody": {
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "required": ["file"],
                                "properties": {
                                    "file": {"type": "string", "format": "binary"},
                                    "notes": {"type": "string"},
                                    "file_last_modified": {"type": "string"},
                                    "language": {"type": "string"},
                                    "min_speakers": {"type": "integer"},
                                    "max_speakers": {"type": "integer"},
                                    "tag_id": {"type": "integer"},
                                    "tag_ids[0]": {"type": "integer"},
                                    "tag_ids[1]": {"type": "integer"}
                                }
                            }
                        }
                    }
                },
                "responses": {"202": {"description": "Upload accepted and queued"}}
            }
        },
        "/tags": {
            "get": {"tags": ["Tags"], "summary": "List tags", "responses": {"200": {"description": "List of tags"}}},
            "post": {"tags": ["Tags"], "summary": "Create tag", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["name"], "properties": {"name": {"type": "string"}, "color": {"type": "string"}, "custom_prompt": {"type": "string"}, "default_language": {"type": "string"}, "default_min_speakers": {"type": "integer"}, "default_max_speakers": {"type": "integer"}}}}}}, "responses": {"201": {"description": "Tag created"}}}
        },
        "/tags/{id}": {
            "put": {"tags": ["Tags"], "summary": "Update tag", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "color": {"type": "string"}, "custom_prompt": {"type": "string"}}}}}}, "responses": {"200": {"description": "Tag updated"}}},
            "delete": {"tags": ["Tags"], "summary": "Delete tag", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Tag deleted"}}}
        },
        "/speakers": {
            "get": {"tags": ["Speakers"], "summary": "List speakers", "responses": {"200": {"description": "List of speakers"}}},
            "post": {"tags": ["Speakers"], "summary": "Create speaker", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["name"], "properties": {"name": {"type": "string"}}}}}}, "responses": {"201": {"description": "Speaker created"}}}
        },
        "/speakers/{id}": {
            "put": {"tags": ["Speakers"], "summary": "Update speaker", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}}}}}}, "responses": {"200": {"description": "Speaker updated"}}},
            "delete": {"tags": ["Speakers"], "summary": "Delete speaker", "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Speaker deleted"}}}
        },
        "/settings/auto-summarization": {
            "put": {
                "tags": ["Settings"],
                "summary": "Toggle auto-summarization",
                "description": "Enable or disable auto-summarization for the current user.",
                "requestBody": {"content": {"application/json": {"schema": {"type": "object", "required": ["enabled"], "properties": {"enabled": {"type": "boolean"}}}}}},
                "responses": {"200": {"description": "Setting updated"}}
            }
        }
    },
    "tags": [
        {"name": "Stats", "description": "System statistics for dashboards"},
        {"name": "Recordings", "description": "Recording CRUD operations"},
        {"name": "Processing", "description": "Transcription and summarization"},
        {"name": "Chat", "description": "Chat with recordings"},
        {"name": "Events", "description": "Calendar events"},
        {"name": "Audio", "description": "Audio file operations"},
        {"name": "Tags", "description": "Tag management"},
        {"name": "Speakers", "description": "Speaker management"},
        {"name": "Batch", "description": "Batch operations"},
        {"name": "Settings", "description": "User settings"}
    ]
}


@api_v1_bp.route('/openapi.json', methods=['GET'])
def get_openapi_spec():
    """Return OpenAPI specification."""
    return jsonify(OPENAPI_SPEC)


@api_v1_bp.route('/docs', methods=['GET'])
def get_docs():
    """Serve Swagger UI documentation."""
    from flask import Response
    html = '''<!DOCTYPE html>
<html>
<head>
    <title>Speakr API v1 Documentation</title>
    <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
</head>
<body>
    <div id="swagger-ui"></div>
    <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
    <script>
        SwaggerUIBundle({
            url: "/api/v1/openapi.json",
            dom_id: '#swagger-ui',
            presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
            layout: "BaseLayout",
            persistAuthorization: true
        });
    </script>
</body>
</html>'''
    return Response(html, mimetype='text/html')


# =============================================================================
# Stats Endpoint (Homepage Widget Compatible)
# =============================================================================

@api_v1_bp.route('/stats', methods=['GET'])
@login_required
def get_stats():
    """
    Get system/user statistics for dashboard widgets.

    Query params:
        scope: 'user' (default) or 'all' (admin only)

    Returns JSON compatible with gethomepage.dev custom API widget:
    {
        "recordings": {"total": N, "completed": N, "processing": N, "pending": N, "failed": N},
        "storage": {"used_bytes": N, "used_human": "X.X GB"},
        "queue": {"jobs_queued": N, "jobs_processing": N},
        "tokens": {"used_this_month": N, "budget": N, "percentage": N},
        "transcription": {"used_this_month_seconds": N, "used_this_month_minutes": N, "budget_seconds": N, "budget_minutes": N, "percentage": N, "estimated_cost": N},
        "activity": {"recordings_today": N, "last_transcription": "ISO datetime"}
    }
    """
    scope = request.args.get('scope', 'user')

    # Admin-only for global stats
    if scope == 'all' and not current_user.is_admin:
        return jsonify({'error': 'Admin access required for global stats'}), 403

    # Build query filters based on scope
    if scope == 'user':
        recording_filter = Recording.user_id == current_user.id
        job_filter = ProcessingJob.user_id == current_user.id
        user_id_for_tokens = current_user.id
    else:
        recording_filter = True  # No filter = all recordings
        job_filter = True
        user_id_for_tokens = None  # Will aggregate all users

    # Recording counts by status
    total = Recording.query.filter(recording_filter).count()
    completed = Recording.query.filter(recording_filter, Recording.status == 'COMPLETED').count()
    processing = Recording.query.filter(
        recording_filter,
        Recording.status.in_(['PROCESSING', 'SUMMARIZING'])
    ).count()
    pending = Recording.query.filter(recording_filter, Recording.status == 'PENDING').count()
    failed = Recording.query.filter(recording_filter, Recording.status == 'FAILED').count()

    # Storage calculation
    storage_query = db.session.query(func.sum(Recording.file_size)).filter(recording_filter)
    storage_bytes = storage_query.scalar() or 0

    # Queue status
    jobs_queued = ProcessingJob.query.filter(
        job_filter,
        ProcessingJob.status == 'queued'
    ).count()
    jobs_processing = ProcessingJob.query.filter(
        job_filter,
        ProcessingJob.status == 'processing'
    ).count()

    # Token usage
    tokens_data = {}
    if user_id_for_tokens:
        # Single user stats
        monthly_usage = token_tracker.get_monthly_usage(user_id_for_tokens)
        user = db.session.get(User, user_id_for_tokens)
        budget = user.monthly_token_budget if user else None

        tokens_data = {
            'used_this_month': monthly_usage,
            'budget': budget,
            'percentage': round((monthly_usage / budget * 100), 1) if budget else None
        }
    else:
        # Aggregate all users (admin scope)
        current_year = date.today().year
        current_month = date.today().month
        total_usage = db.session.query(func.sum(TokenUsage.total_tokens)).filter(
            extract('year', TokenUsage.date) == current_year,
            extract('month', TokenUsage.date) == current_month
        ).scalar() or 0

        tokens_data = {
            'used_this_month': total_usage,
            'budget': None,
            'percentage': None
        }

    # Transcription usage
    transcription_data = {}
    if user_id_for_tokens:
        # Single user stats
        monthly_transcription = transcription_tracker.get_monthly_usage(user_id_for_tokens)
        monthly_cost = transcription_tracker.get_monthly_cost(user_id_for_tokens)
        user = db.session.get(User, user_id_for_tokens)
        transcription_budget = user.monthly_transcription_budget if user else None

        transcription_data = {
            'used_this_month_seconds': monthly_transcription,
            'used_this_month_minutes': monthly_transcription // 60,
            'budget_seconds': transcription_budget,
            'budget_minutes': transcription_budget // 60 if transcription_budget else None,
            'percentage': round((monthly_transcription / transcription_budget * 100), 1) if transcription_budget else None,
            'estimated_cost': round(monthly_cost, 4)
        }
    else:
        # Aggregate all users (admin scope)
        current_year = date.today().year
        current_month = date.today().month
        total_seconds = db.session.query(func.sum(TranscriptionUsage.audio_duration_seconds)).filter(
            extract('year', TranscriptionUsage.date) == current_year,
            extract('month', TranscriptionUsage.date) == current_month
        ).scalar() or 0
        total_cost = db.session.query(func.sum(TranscriptionUsage.estimated_cost)).filter(
            extract('year', TranscriptionUsage.date) == current_year,
            extract('month', TranscriptionUsage.date) == current_month
        ).scalar() or 0

        transcription_data = {
            'used_this_month_seconds': total_seconds,
            'used_this_month_minutes': total_seconds // 60,
            'budget_seconds': None,
            'budget_minutes': None,
            'percentage': None,
            'estimated_cost': round(total_cost, 4)
        }

    # Recent activity
    today_start = datetime.combine(date.today(), datetime.min.time())
    recordings_today = Recording.query.filter(
        recording_filter,
        Recording.created_at >= today_start
    ).count()

    # Last completed transcription
    last_completed = Recording.query.filter(
        recording_filter,
        Recording.status == 'COMPLETED',
        Recording.completed_at.isnot(None)
    ).order_by(Recording.completed_at.desc()).first()

    last_transcription = last_completed.completed_at.isoformat() if last_completed and last_completed.completed_at else None

    # Build response
    response = {
        'recordings': {
            'total': total,
            'completed': completed,
            'processing': processing,
            'pending': pending,
            'failed': failed
        },
        'storage': {
            'used_bytes': storage_bytes,
            'used_human': format_bytes(storage_bytes)
        },
        'queue': {
            'jobs_queued': jobs_queued,
            'jobs_processing': jobs_processing
        },
        'tokens': tokens_data,
        'transcription': transcription_data,
        'activity': {
            'recordings_today': recordings_today,
            'last_transcription': last_transcription
        }
    }

    # Add user counts for admin scope
    if scope == 'all' and current_user.is_admin:
        total_users = User.query.count()
        # Active = users with recordings in last 30 days
        cutoff = datetime.utcnow() - timedelta(days=30)
        active_users = db.session.query(func.count(func.distinct(Recording.user_id))).filter(
            Recording.created_at >= cutoff
        ).scalar() or 0

        response['users'] = {
            'total': total_users,
            'active': active_users
        }

    return jsonify(response)


# =============================================================================
# Recordings List with Enhanced Filtering
# =============================================================================

@api_v1_bp.route('/recordings', methods=['GET'])
@login_required
def list_recordings():
    """
    List recordings with filtering and pagination.

    Query params:
        page: Page number (default: 1)
        per_page: Items per page (default: 25, max: 100)
        status: Filter by status (pending, processing, completed, failed, all)
        sort_by: Sort field (created_at, meeting_date, title, file_size, status)
        sort_order: asc or desc (default: desc)
        date_from: Filter from date (ISO format)
        date_to: Filter to date (ISO format)
        tag_id: Filter by tag ID
        q: Search query (title, participants)
        inbox: Filter by inbox status (true/false)
        starred: Filter by starred status (true/false)
    """
    # Parse query parameters
    page = request.args.get('page', 1, type=int)
    per_page = min(request.args.get('per_page', 25, type=int), 100)
    status_filter = request.args.get('status', 'all').lower()
    sort_by = request.args.get('sort_by', 'created_at')
    sort_order = request.args.get('sort_order', 'desc').lower()
    date_from = request.args.get('date_from')
    date_to = request.args.get('date_to')
    tag_id = request.args.get('tag_id', type=int)
    search_query = request.args.get('q', '').strip()
    inbox_filter = request.args.get('inbox')
    starred_filter = request.args.get('starred')

    # Base query - user's recordings
    query = Recording.query.filter(Recording.user_id == current_user.id)

    # Status filter
    if status_filter == 'pending':
        query = query.filter(Recording.status == 'PENDING')
    elif status_filter == 'processing':
        query = query.filter(Recording.status.in_(['PROCESSING', 'SUMMARIZING']))
    elif status_filter == 'completed':
        query = query.filter(Recording.status == 'COMPLETED')
    elif status_filter == 'failed':
        query = query.filter(Recording.status == 'FAILED')
    # 'all' = no status filter

    # Date filters
    if date_from:
        try:
            from_date = datetime.fromisoformat(date_from.replace('Z', '+00:00'))
            query = query.filter(Recording.created_at >= from_date)
        except ValueError:
            pass

    if date_to:
        try:
            to_date = datetime.fromisoformat(date_to.replace('Z', '+00:00'))
            query = query.filter(Recording.created_at <= to_date)
        except ValueError:
            pass

    # Tag filter
    if tag_id:
        query = query.join(RecordingTag).filter(RecordingTag.tag_id == tag_id)

    # Search filter
    if search_query:
        search_pattern = f'%{search_query}%'
        query = query.filter(
            or_(
                Recording.title.ilike(search_pattern),
                Recording.participants.ilike(search_pattern)
            )
        )

    # Inbox filter
    if inbox_filter is not None:
        is_inbox = inbox_filter.lower() == 'true'
        query = query.filter(Recording.is_inbox == is_inbox)

    # Starred filter
    if starred_filter is not None:
        is_starred = starred_filter.lower() == 'true'
        query = query.filter(Recording.is_highlighted == is_starred)

    # Sorting
    sort_columns = {
        'created_at': Recording.created_at,
        'meeting_date': Recording.meeting_date,
        'title': Recording.title,
        'file_size': Recording.file_size,
        'status': Recording.status
    }
    sort_column = sort_columns.get(sort_by, Recording.created_at)

    if sort_order == 'asc':
        query = query.order_by(sort_column.asc())
    else:
        query = query.order_by(sort_column.desc())

    # Pagination
    pagination = query.paginate(page=page, per_page=per_page, error_out=False)

    # Build response
    recordings = []
    for r in pagination.items:
        recordings.append({
            'id': r.id,
            'title': r.title,
            'status': r.status,
            'created_at': r.created_at.isoformat() if r.created_at else None,
            'meeting_date': r.meeting_date.isoformat() if r.meeting_date else None,
            'file_size': r.file_size,
            'original_filename': r.original_filename,
            'participants': r.participants,
            'is_inbox': r.is_inbox,
            'is_highlighted': r.is_highlighted,
            'audio_available': r.audio_deleted_at is None,
            'has_transcription': bool(r.transcription),
            'has_summary': bool(r.summary),
            'error_message': r.error_message if r.status == 'FAILED' else None,
            'tags': [{'id': t.id, 'name': t.name, 'color': t.color} for t in r.tags]
        })

    return jsonify({
        'recordings': recordings,
        'pagination': {
            'page': pagination.page,
            'per_page': pagination.per_page,
            'total': pagination.total,
            'total_pages': pagination.pages,
            'has_next': pagination.has_next,
            'has_prev': pagination.has_prev
        }
    })


# =============================================================================
# Recording Detail
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>', methods=['GET'])
@login_required
def get_recording(recording_id):
    """
    Get full recording details.

    Query params:
        include: Comma-separated fields to include (transcription, summary, notes)
                 Default: all fields
        format: 'full' (default) or 'minimal' (excludes large text fields)
    """
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    include = request.args.get('include', 'transcription,summary,notes')
    include_fields = [f.strip() for f in include.split(',')]
    format_type = request.args.get('format', 'full')

    response = {
        'id': recording.id,
        'title': recording.title,
        'status': recording.status,
        'participants': recording.participants,
        'created_at': recording.created_at.isoformat() if recording.created_at else None,
        'meeting_date': recording.meeting_date.isoformat() if recording.meeting_date else None,
        'completed_at': recording.completed_at.isoformat() if recording.completed_at else None,
        'file_size': recording.file_size,
        'original_filename': recording.original_filename,
        'mime_type': recording.mime_type,
        'is_inbox': recording.is_inbox,
        'is_highlighted': recording.is_highlighted,
        'audio_available': recording.audio_deleted_at is None,
        'processing_time_seconds': recording.processing_time_seconds,
        'error_message': recording.error_message if recording.status == 'FAILED' else None,
        'tags': [{'id': t.id, 'name': t.name, 'color': t.color} for t in recording.tags],
        'duplicate_info': recording.get_duplicate_info()
    }

    # Include large text fields based on params
    if format_type != 'minimal':
        if 'transcription' in include_fields:
            # Format transcription using user's default template
            response['transcription'] = format_transcription_with_template(
                recording.transcription, current_user
            ) if recording.transcription else None
        if 'summary' in include_fields:
            response['summary'] = recording.summary
        if 'notes' in include_fields:
            response['notes'] = recording.notes

    return jsonify(response)


# =============================================================================
# Recording Transcript/Summary/Notes Individual Endpoints
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>/transcript', methods=['GET'])
@login_required
def get_transcript(recording_id):
    """
    Get transcript in various formats.

    Query params:
        format: json (default), text, srt, vtt
    """
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    if not recording.transcription:
        return jsonify({'error': 'No transcription available'}), 404

    format_type = request.args.get('format', 'json').lower()

    if format_type == 'json':
        try:
            segments = json.loads(recording.transcription)
            return jsonify({
                'format': 'json',
                'segments': segments
            })
        except json.JSONDecodeError:
            return jsonify({
                'format': 'json',
                'segments': [],
                'raw': recording.transcription
            })

    elif format_type == 'text':
        # Use user's default template for text format
        formatted = format_transcription_with_template(recording.transcription, current_user)
        return jsonify({
            'format': 'text',
            'content': formatted
        })

    elif format_type in ['srt', 'vtt']:
        try:
            segments = json.loads(recording.transcription)
            lines = []

            if format_type == 'vtt':
                lines.append('WEBVTT')
                lines.append('')

            for i, seg in enumerate(segments, 1):
                start = seg.get('start_time', seg.get('start', 0))
                end = seg.get('end_time', seg.get('end', start + 1))
                text = seg.get('sentence') or seg.get('text', '')
                speaker = seg.get('speaker', '')

                # Format timestamps
                def fmt_time(seconds, use_comma=False):
                    h = int(seconds // 3600)
                    m = int((seconds % 3600) // 60)
                    s = int(seconds % 60)
                    ms = int((seconds - int(seconds)) * 1000)
                    sep = ',' if use_comma else '.'
                    return f"{h:02d}:{m:02d}:{s:02d}{sep}{ms:03d}"

                if format_type == 'srt':
                    lines.append(str(i))
                    lines.append(f"{fmt_time(start, True)} --> {fmt_time(end, True)}")
                else:
                    lines.append(f"{fmt_time(start)} --> {fmt_time(end)}")

                if speaker:
                    lines.append(f"<v {speaker}>{text}")
                else:
                    lines.append(text)
                lines.append('')

            return jsonify({
                'format': format_type,
                'content': '\n'.join(lines)
            })
        except (json.JSONDecodeError, TypeError):
            return jsonify({'error': 'Cannot generate subtitle format from transcript'}), 400

    return jsonify({'error': f'Unknown format: {format_type}'}), 400


@api_v1_bp.route('/recordings/<int:recording_id>/summary', methods=['GET'])
@login_required
def get_summary(recording_id):
    """Get summary markdown."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    return jsonify({
        'summary': recording.summary,
        'has_summary': bool(recording.summary)
    })


@api_v1_bp.route('/recordings/<int:recording_id>/notes', methods=['GET'])
@login_required
def get_notes(recording_id):
    """Get notes markdown."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    return jsonify({
        'notes': recording.notes,
        'has_notes': bool(recording.notes)
    })


# =============================================================================
# Recording Update Operations
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>', methods=['PATCH'])
@login_required
def update_recording(recording_id):
    """
    Update recording metadata, notes, or summary.

    Request body (all fields optional):
    {
        "title": "Updated Title",
        "participants": "Alice, Bob",
        "notes": "Updated notes...",
        "summary": "Updated summary...",
        "meeting_date": "2024-01-15T09:00:00Z",
        "is_inbox": false,
        "is_highlighted": true
    }
    """
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user, require_edit=True):
        return jsonify({'error': 'Permission denied'}), 403

    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    # Update fields if provided
    if 'title' in data:
        recording.title = data['title']
    if 'participants' in data:
        recording.participants = data['participants']
    if 'notes' in data:
        recording.notes = data['notes']
    if 'summary' in data:
        recording.summary = data['summary']
    if 'meeting_date' in data:
        try:
            if data['meeting_date']:
                recording.meeting_date = datetime.fromisoformat(data['meeting_date'].replace('Z', '+00:00'))
            else:
                recording.meeting_date = None
        except ValueError:
            return jsonify({'error': 'Invalid meeting_date format'}), 400
    if 'is_inbox' in data:
        recording.is_inbox = bool(data['is_inbox'])
    if 'is_highlighted' in data:
        recording.is_highlighted = bool(data['is_highlighted'])

    db.session.commit()

    return jsonify({
        'success': True,
        'recording': {
            'id': recording.id,
            'title': recording.title,
            'participants': recording.participants,
            'notes': recording.notes,
            'summary': recording.summary,
            'meeting_date': recording.meeting_date.isoformat() if recording.meeting_date else None,
            'is_inbox': recording.is_inbox,
            'is_highlighted': recording.is_highlighted
        }
    })


@api_v1_bp.route('/recordings/<int:recording_id>/notes', methods=['PUT'])
@login_required
def replace_notes(recording_id):
    """Replace notes entirely."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user, require_edit=True):
        return jsonify({'error': 'Permission denied'}), 403

    data = request.get_json()
    if not data or 'notes' not in data:
        return jsonify({'error': 'notes field required'}), 400

    recording.notes = data['notes']
    db.session.commit()

    return jsonify({'success': True, 'notes': recording.notes})


@api_v1_bp.route('/recordings/<int:recording_id>/summary', methods=['PUT'])
@login_required
def replace_summary(recording_id):
    """Replace summary entirely."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user, require_edit=True):
        return jsonify({'error': 'Permission denied'}), 403

    data = request.get_json()
    if not data or 'summary' not in data:
        return jsonify({'error': 'summary field required'}), 400

    recording.summary = data['summary']
    db.session.commit()

    return jsonify({'success': True, 'summary': recording.summary})


# =============================================================================
# Recording Delete
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>', methods=['DELETE'])
@login_required
def delete_recording(recording_id):
    """Delete a recording."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    # Check ownership (only owner can delete)
    if recording.user_id != current_user.id:
        return jsonify({'error': 'Permission denied - only owner can delete'}), 403

    # Check if deletion is allowed
    USERS_CAN_DELETE = os.environ.get('USERS_CAN_DELETE', 'true').lower() == 'true'
    if not USERS_CAN_DELETE and not current_user.is_admin:
        return jsonify({'error': 'Deletion not allowed'}), 403

    # Delete associated files
    if recording.audio_path:
        try:
            audio_path = os.path.join(current_app.config.get('UPLOAD_FOLDER', 'uploads'), recording.audio_path)
            if os.path.exists(audio_path):
                os.remove(audio_path)
        except Exception:
            pass  # Continue with DB deletion even if file deletion fails

    # Delete from database
    db.session.delete(recording)
    db.session.commit()

    return jsonify({'success': True, 'message': 'Recording deleted'})


# =============================================================================
# Recording Status
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>/status', methods=['GET'])
@login_required
def get_recording_status(recording_id):
    """Get processing status of a recording."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    # Get queue position if pending/processing
    queue_position = None
    if recording.status in ['PENDING', 'PROCESSING', 'SUMMARIZING']:
        # Count jobs ahead of this one
        job = ProcessingJob.query.filter_by(
            recording_id=recording_id,
            status='queued'
        ).first()

        if job:
            queue_position = ProcessingJob.query.filter(
                ProcessingJob.status == 'queued',
                ProcessingJob.created_at < job.created_at
            ).count() + 1

    return jsonify({
        'id': recording.id,
        'status': recording.status,
        'queue_position': queue_position,
        'error_message': recording.error_message if recording.status == 'FAILED' else None,
        'completed_at': recording.completed_at.isoformat() if recording.completed_at else None
    })


# =============================================================================
# Tag Management
# =============================================================================

@api_v1_bp.route('/tags', methods=['GET'])
@login_required
def list_tags():
    """List available tags (personal + group tags user has access to)."""
    from src.models.organization import GroupMembership

    # Get user's personal tags
    user_tags = Tag.query.filter_by(user_id=current_user.id, group_id=None).order_by(Tag.name).all()

    # Get user's team memberships
    memberships = GroupMembership.query.filter_by(user_id=current_user.id).all()
    team_roles = {m.group_id: m.role for m in memberships}
    team_ids = list(team_roles.keys())

    # Get group tags
    team_tags = []
    if team_ids:
        team_tags = Tag.query.filter(Tag.group_id.in_(team_ids)).order_by(Tag.name).all()

    result = []

    # Personal tags
    for tag in user_tags:
        result.append({
            'id': tag.id,
            'name': tag.name,
            'color': tag.color,
            'is_group_tag': False,
            'group_id': None,
            'custom_prompt': tag.custom_prompt,
            'default_language': tag.default_language,
            'default_min_speakers': tag.default_min_speakers,
            'default_max_speakers': tag.default_max_speakers,
            'protect_from_deletion': tag.protect_from_deletion,
            'can_edit': True
        })

    # Group tags
    for tag in team_tags:
        user_role = team_roles.get(tag.group_id, 'member')
        result.append({
            'id': tag.id,
            'name': tag.name,
            'color': tag.color,
            'is_group_tag': True,
            'group_id': tag.group_id,
            'custom_prompt': tag.custom_prompt,
            'default_language': tag.default_language,
            'default_min_speakers': tag.default_min_speakers,
            'default_max_speakers': tag.default_max_speakers,
            'protect_from_deletion': tag.protect_from_deletion,
            'can_edit': (user_role == 'admin')
        })

    return jsonify({'tags': result})


@api_v1_bp.route('/tags', methods=['POST'])
@login_required
def create_tag():
    """Create a new tag."""
    from src.models.organization import GroupMembership

    data = request.get_json()
    if not data or not data.get('name'):
        return jsonify({'error': 'Tag name is required'}), 400

    group_id = data.get('group_id')

    # If group tag, verify admin permission
    if group_id:
        membership = GroupMembership.query.filter_by(
            group_id=group_id,
            user_id=current_user.id
        ).first()
        if not membership or membership.role != 'admin':
            return jsonify({'error': 'Only group admins can create group tags'}), 403

        # Check for duplicate
        existing = Tag.query.filter_by(name=data['name'], group_id=group_id).first()
        if existing:
            return jsonify({'error': 'Tag with this name already exists for this group'}), 400
    else:
        # Check for duplicate personal tag
        existing = Tag.query.filter_by(name=data['name'], user_id=current_user.id, group_id=None).first()
        if existing:
            return jsonify({'error': 'Tag with this name already exists'}), 400

    tag = Tag(
        name=data['name'],
        user_id=current_user.id,
        group_id=group_id,
        color=data.get('color', '#3B82F6'),
        custom_prompt=data.get('custom_prompt'),
        default_language=data.get('default_language'),
        default_min_speakers=data.get('default_min_speakers'),
        default_max_speakers=data.get('default_max_speakers'),
        protect_from_deletion=data.get('protect_from_deletion', False)
    )

    db.session.add(tag)
    db.session.commit()

    return jsonify({
        'id': tag.id,
        'name': tag.name,
        'color': tag.color,
        'is_group_tag': tag.group_id is not None,
        'group_id': tag.group_id,
        'custom_prompt': tag.custom_prompt,
        'default_language': tag.default_language,
        'default_min_speakers': tag.default_min_speakers,
        'default_max_speakers': tag.default_max_speakers,
        'protect_from_deletion': tag.protect_from_deletion
    }), 201


@api_v1_bp.route('/tags/<int:tag_id>', methods=['PUT'])
@login_required
def update_tag(tag_id):
    """Update a tag."""
    from src.models.organization import GroupMembership

    tag = db.session.get(Tag, tag_id)
    if not tag:
        return jsonify({'error': 'Tag not found'}), 404

    # Check permission
    if tag.group_id:
        membership = GroupMembership.query.filter_by(
            group_id=tag.group_id,
            user_id=current_user.id
        ).first()
        if not membership or membership.role != 'admin':
            return jsonify({'error': 'Only group admins can edit group tags'}), 403
    else:
        if tag.user_id != current_user.id:
            return jsonify({'error': 'Permission denied'}), 403

    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    if 'name' in data:
        tag.name = data['name']
    if 'color' in data:
        tag.color = data['color']
    if 'custom_prompt' in data:
        tag.custom_prompt = data['custom_prompt']
    if 'default_language' in data:
        tag.default_language = data['default_language']
    if 'default_min_speakers' in data:
        tag.default_min_speakers = data['default_min_speakers']
    if 'default_max_speakers' in data:
        tag.default_max_speakers = data['default_max_speakers']
    if 'protect_from_deletion' in data:
        tag.protect_from_deletion = data['protect_from_deletion']

    db.session.commit()

    return jsonify({'success': True, 'tag': {
        'id': tag.id,
        'name': tag.name,
        'color': tag.color,
        'custom_prompt': tag.custom_prompt,
        'default_language': tag.default_language,
        'default_min_speakers': tag.default_min_speakers,
        'default_max_speakers': tag.default_max_speakers,
        'protect_from_deletion': tag.protect_from_deletion
    }})


@api_v1_bp.route('/tags/<int:tag_id>', methods=['DELETE'])
@login_required
def delete_tag(tag_id):
    """Delete a tag."""
    from src.models.organization import GroupMembership

    tag = db.session.get(Tag, tag_id)
    if not tag:
        return jsonify({'error': 'Tag not found'}), 404

    # Check permission
    if tag.group_id:
        membership = GroupMembership.query.filter_by(
            group_id=tag.group_id,
            user_id=current_user.id
        ).first()
        if not membership or membership.role != 'admin':
            return jsonify({'error': 'Only group admins can delete group tags'}), 403
    else:
        if tag.user_id != current_user.id:
            return jsonify({'error': 'Permission denied'}), 403

    # Remove all recording associations
    RecordingTag.query.filter_by(tag_id=tag_id).delete()

    db.session.delete(tag)
    db.session.commit()

    return jsonify({'success': True, 'message': 'Tag deleted'})


@api_v1_bp.route('/recordings/<int:recording_id>/tags', methods=['POST'])
@login_required
def add_tags_to_recording(recording_id):
    """Add tag(s) to a recording."""
    from src.models.organization import GroupMembership

    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    data = request.get_json()
    tag_ids = data.get('tag_ids', [])
    if not tag_ids:
        # Support single tag_id for backward compatibility
        tag_id = data.get('tag_id')
        if tag_id:
            tag_ids = [tag_id]
        else:
            return jsonify({'error': 'tag_ids or tag_id required'}), 400

    added_tags = []
    errors = []

    for tag_id in tag_ids:
        tag = db.session.get(Tag, tag_id)
        if not tag:
            errors.append(f'Tag {tag_id} not found')
            continue

        # Check permission for this tag
        if tag.group_id:
            membership = GroupMembership.query.filter_by(
                group_id=tag.group_id,
                user_id=current_user.id
            ).first()
            if not membership:
                errors.append(f'No access to tag {tag_id}')
                continue
        else:
            if tag.user_id != current_user.id:
                errors.append(f'No access to tag {tag_id}')
                continue

        # Check if already exists
        existing = RecordingTag.query.filter_by(
            recording_id=recording_id,
            tag_id=tag_id
        ).first()
        if existing:
            continue  # Skip, already added

        # Get next order position
        max_order = db.session.query(func.max(RecordingTag.order)).filter_by(
            recording_id=recording_id
        ).scalar() or 0

        recording_tag = RecordingTag(
            recording_id=recording_id,
            tag_id=tag_id,
            order=max_order + 1
        )
        db.session.add(recording_tag)
        added_tags.append({'id': tag.id, 'name': tag.name})

    db.session.commit()

    return jsonify({
        'success': True,
        'added_tags': added_tags,
        'errors': errors if errors else None
    })


@api_v1_bp.route('/recordings/<int:recording_id>/tags/<int:tag_id>', methods=['DELETE'])
@login_required
def remove_tag_from_recording(recording_id, tag_id):
    """Remove a tag from a recording."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user, require_edit=True):
        return jsonify({'error': 'Permission denied'}), 403

    recording_tag = RecordingTag.query.filter_by(
        recording_id=recording_id,
        tag_id=tag_id
    ).first()

    if not recording_tag:
        return jsonify({'error': 'Tag not on this recording'}), 404

    db.session.delete(recording_tag)
    db.session.commit()

    return jsonify({'success': True, 'message': 'Tag removed'})


# =============================================================================
# Speaker Management
# =============================================================================

@api_v1_bp.route('/speakers', methods=['GET'])
@login_required
def list_speakers():
    """List all speakers for the current user."""
    speakers = Speaker.query.filter_by(user_id=current_user.id)\
                           .order_by(Speaker.use_count.desc(), Speaker.last_used.desc())\
                           .all()

    return jsonify({
        'speakers': [{
            'id': s.id,
            'name': s.name,
            'use_count': s.use_count,
            'last_used': s.last_used.isoformat() if s.last_used else None,
            'confidence_score': s.confidence_score,
            'has_voice_profile': s.average_embedding is not None
        } for s in speakers]
    })


@api_v1_bp.route('/speakers', methods=['POST'])
@login_required
def create_speaker():
    """Create a new speaker."""
    data = request.get_json()
    if not data or not data.get('name'):
        return jsonify({'error': 'Speaker name is required'}), 400

    name = data['name'].strip()

    # Check if already exists
    existing = Speaker.query.filter_by(user_id=current_user.id, name=name).first()
    if existing:
        return jsonify({'error': 'Speaker with this name already exists'}), 400

    speaker = Speaker(
        name=name,
        user_id=current_user.id,
        use_count=0,
        created_at=datetime.utcnow()
    )
    db.session.add(speaker)
    db.session.commit()

    return jsonify({
        'id': speaker.id,
        'name': speaker.name,
        'use_count': speaker.use_count,
        'created_at': speaker.created_at.isoformat()
    }), 201


@api_v1_bp.route('/speakers/<int:speaker_id>', methods=['PUT'])
@login_required
def update_speaker(speaker_id):
    """Update a speaker (cascades name changes to recordings)."""
    speaker = db.session.get(Speaker, speaker_id)
    if not speaker:
        return jsonify({'error': 'Speaker not found'}), 404

    if speaker.user_id != current_user.id:
        return jsonify({'error': 'Permission denied'}), 403

    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    old_name = speaker.name
    new_name = data.get('name', '').strip()

    if not new_name:
        return jsonify({'error': 'Speaker name is required'}), 400

    if new_name != old_name:
        # Update speaker name
        speaker.name = new_name

        # Update all recordings that have this speaker in their transcription
        from src.services.speaker import update_speaker_in_recordings
        try:
            update_speaker_in_recordings(current_user.id, old_name, new_name)
        except Exception as e:
            current_app.logger.error(f"Error updating speaker in recordings: {e}")

    db.session.commit()

    return jsonify({
        'success': True,
        'speaker': {
            'id': speaker.id,
            'name': speaker.name,
            'use_count': speaker.use_count
        }
    })


@api_v1_bp.route('/speakers/<int:speaker_id>', methods=['DELETE'])
@login_required
def delete_speaker(speaker_id):
    """Delete a speaker."""
    speaker = db.session.get(Speaker, speaker_id)
    if not speaker:
        return jsonify({'error': 'Speaker not found'}), 404

    if speaker.user_id != current_user.id:
        return jsonify({'error': 'Permission denied'}), 403

    db.session.delete(speaker)
    db.session.commit()

    return jsonify({'success': True, 'message': 'Speaker deleted'})


@api_v1_bp.route('/recordings/<int:recording_id>/speakers', methods=['GET'])
@login_required
def get_recording_speakers(recording_id):
    """Get speakers in a recording with suggestions."""
    from src.services.speaker_embedding_matcher import find_matching_speakers

    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    # Parse transcription to get speakers
    speakers_in_recording = []
    speaker_counts = {}

    if recording.transcription:
        try:
            segments = json.loads(recording.transcription)
            for seg in segments:
                speaker = seg.get('speaker', 'Unknown')
                speaker_counts[speaker] = speaker_counts.get(speaker, 0) + 1
        except (json.JSONDecodeError, TypeError):
            pass

    # Build speaker list with identification info
    for label, count in speaker_counts.items():
        # Check if this speaker label has been identified
        identified_name = None
        speaker_id = None

        # Look for speaker in user's speakers by checking recordings
        # This is a simplified check - actual implementation would check speaker_embeddings
        speakers_in_recording.append({
            'label': label,
            'identified_name': identified_name,
            'speaker_id': speaker_id,
            'segment_count': count
        })

    # Get voice-based suggestions
    suggestions = {}
    if recording.speaker_embeddings:
        try:
            matches = find_matching_speakers(current_user.id, recording.speaker_embeddings)
            for label, speaker_matches in matches.items():
                suggestions[label] = [{
                    'speaker_id': m['speaker_id'],
                    'name': m['name'],
                    'similarity': round(m['similarity'] * 100, 1)
                } for m in speaker_matches[:3]]
        except Exception as e:
            current_app.logger.error(f"Error getting speaker suggestions: {e}")

    return jsonify({
        'speakers': speakers_in_recording,
        'suggestions': suggestions
    })


@api_v1_bp.route('/recordings/<int:recording_id>/speakers/assign', methods=['PUT'])
@login_required
def assign_speakers(recording_id):
    """
    Assign speaker names to a recording's transcription segments.

    Accepts the same speaker_map format as the web UI, plus convenience
    formats for API callers (plain strings).

    Request body:
    {
        "speaker_map": {
            "SPEAKER_00": "Jane Doe",                       // string shorthand
            "SPEAKER_01": {"name": "Bob", "isMe": false}    // full object
        },
        "regenerate_summary": false
    }
    """
    from src.services.speaker import update_speaker_usage
    from src.services.speaker_embedding_matcher import update_speaker_embedding
    from src.services.speaker_snippets import create_speaker_snippets
    from src.services.job_queue import job_queue

    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': 'Recording not found'}), 404

        if not has_recording_access(recording, current_user, require_edit=True):
            return jsonify({'error': 'Permission denied'}), 403

        data = request.get_json()
        if not data or 'speaker_map' not in data:
            return jsonify({'error': 'speaker_map is required'}), 400

        raw_speaker_map = data['speaker_map']
        regenerate_summary = data.get('regenerate_summary', False)

        if not isinstance(raw_speaker_map, dict):
            return jsonify({'error': 'speaker_map must be an object'}), 400

        # Normalize values to {name, isMe} format (same as web UI expects)
        speaker_map = {}
        for label, value in raw_speaker_map.items():
            if isinstance(value, str):
                speaker_map[label] = {'name': value.strip(), 'isMe': False}
            elif isinstance(value, dict):
                speaker_map[label] = {
                    'name': value.get('name', '').strip() if value.get('name') else '',
                    'isMe': value.get('isMe', False)
                }
            else:
                return jsonify({'error': f'Invalid value type for speaker "{label}"'}), 400

        # --- Apply names to transcription (same logic as update_speakers in recordings.py) ---
        transcription_text = recording.transcription or ''
        is_json = False
        try:
            transcription_data = json.loads(transcription_text)
            is_json = isinstance(transcription_data, list)
        except (json.JSONDecodeError, TypeError):
            is_json = False

        speaker_names_used = []

        if is_json:
            for segment in transcription_data:
                original_speaker_label = segment.get('speaker')
                if original_speaker_label in speaker_map:
                    new_name_info = speaker_map[original_speaker_label]
                    new_name = new_name_info.get('name', '').strip()
                    if new_name_info.get('isMe') and not new_name:
                        new_name = current_user.name or 'Me'
                    if new_name:
                        segment['speaker'] = new_name
                        if new_name not in speaker_names_used:
                            speaker_names_used.append(new_name)

            recording.transcription = json.dumps(transcription_data)

            # Update participants - exclude unresolved SPEAKER_XX labels
            final_speakers = set()
            for seg in transcription_data:
                speaker = seg.get('speaker')
                if speaker and str(speaker).strip():
                    if not re.match(r'^SPEAKER_\d+$', str(speaker), re.IGNORECASE):
                        final_speakers.add(speaker)
            recording.participants = ', '.join(sorted(list(final_speakers)))
        else:
            # Plain text transcript
            new_participants = []
            for speaker_label, new_name_info in speaker_map.items():
                new_name = new_name_info.get('name', '').strip()
                if new_name_info.get('isMe') and not new_name:
                    new_name = current_user.name or 'Me'
                if new_name:
                    transcription_text = re.sub(
                        r'\[\s*' + re.escape(speaker_label) + r'\s*\]',
                        f'[{new_name}]',
                        transcription_text,
                        flags=re.IGNORECASE
                    )
                    if new_name not in new_participants:
                        new_participants.append(new_name)

            recording.transcription = transcription_text
            recording.participants = ', '.join(new_participants)
            speaker_names_used = new_participants

        # Update speaker usage statistics
        if speaker_names_used:
            update_speaker_usage(speaker_names_used)

        # Update speaker voice embeddings if available
        embeddings_updated = 0
        snippets_created = 0
        if recording.speaker_embeddings and speaker_map:
            try:
                embeddings_data = json.loads(recording.speaker_embeddings) if isinstance(recording.speaker_embeddings, str) else recording.speaker_embeddings

                speaker_label_to_name = {}
                for speaker_label, speaker_info in speaker_map.items():
                    name = speaker_info.get('name', '').strip()
                    if speaker_info.get('isMe') and not name:
                        name = current_user.name or 'Me'
                    if name and not re.match(r'^SPEAKER_\d+$', name, re.IGNORECASE):
                        speaker_label_to_name[speaker_label] = name

                for speaker_label, embedding in embeddings_data.items():
                    if speaker_label in speaker_label_to_name and embedding and len(embedding) == 256:
                        speaker_name = speaker_label_to_name[speaker_label]
                        speaker_obj = Speaker.query.filter_by(
                            user_id=current_user.id,
                            name=speaker_name
                        ).first()
                        if speaker_obj:
                            update_speaker_embedding(speaker_obj, embedding, recording.id)
                            embeddings_updated += 1

                if speaker_label_to_name:
                    snippets_created = create_speaker_snippets(recording.id, speaker_map)
            except Exception as e:
                current_app.logger.error(f"Error updating speaker embeddings: {e}", exc_info=True)

        db.session.commit()

        summary_queued = False
        if regenerate_summary:
            job_queue.enqueue(
                user_id=current_user.id,
                recording_id=recording.id,
                job_type='summarize',
                params={'user_id': current_user.id}
            )
            summary_queued = True

        return jsonify({
            'success': True,
            'message': 'Speakers updated successfully.',
            'recording': {
                'id': recording.id,
                'title': recording.title,
                'participants': recording.participants,
                'status': recording.status
            },
            'summary_queued': summary_queued,
            'embeddings_updated': embeddings_updated,
            'snippets_created': snippets_created
        })

    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Error assigning speakers for recording {recording_id}: {e}", exc_info=True)
        return jsonify({'error': str(e)}), 500


@api_v1_bp.route('/recordings/<int:recording_id>/speakers/identify', methods=['POST'])
@login_required
def identify_speakers(recording_id):
    """
    Trigger LLM-based auto-identification of speakers from transcript context.
    Returns suggestions only - does not modify the recording.

    Uses the shared identification service with JSON schema support,
    name sanitization, and fallback logic.
    """
    from src.services.speaker_identification import identify_speakers_from_transcript

    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': 'Recording not found'}), 404

        if not has_recording_access(recording, current_user):
            return jsonify({'error': 'Permission denied'}), 403

        if not recording.transcription:
            return jsonify({'error': 'No transcription available for speaker identification'}), 400

        try:
            transcription_data = json.loads(recording.transcription)
        except (json.JSONDecodeError, TypeError):
            return jsonify({'error': 'Transcription format not supported for auto-identification'}), 400

        if not isinstance(transcription_data, list):
            return jsonify({'error': 'Transcription format not supported for auto-identification'}), 400

        speaker_map = identify_speakers_from_transcript(transcription_data, current_user.id)

        if not speaker_map:
            return jsonify({'error': 'No speakers found in transcription'}), 400

        return jsonify({'success': True, 'speaker_map': speaker_map})

    except ValueError as ve:
        return jsonify({'error': str(ve)}), 503
    except Exception as e:
        current_app.logger.error(f"Error during auto speaker identification for recording {recording_id}: {e}", exc_info=True)
        return jsonify({'error': f'An unexpected error occurred: {str(e)}'}), 500


# =============================================================================
# Processing Operations
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>/transcribe', methods=['POST'])
@login_required
def start_transcription(recording_id):
    """Queue transcription for a recording."""
    from src.services.job_queue import job_queue

    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user, require_edit=True):
        return jsonify({'error': 'Permission denied'}), 403

    # Check if audio is available
    if recording.audio_deleted_at:
        return jsonify({'error': 'Audio has been deleted'}), 400

    data = request.get_json() or {}

    params = {
        'language': data.get('language'),
        'min_speakers': data.get('min_speakers'),
        'max_speakers': data.get('max_speakers')
    }

    # Queue the job
    job_id = job_queue.enqueue(
        user_id=current_user.id,
        recording_id=recording_id,
        job_type='reprocess_transcription',
        params={k: v for k, v in params.items() if v is not None}
    )

    return jsonify({
        'success': True,
        'job_id': job_id,
        'status': 'QUEUED',
        'message': 'Transcription queued'
    })


@api_v1_bp.route('/recordings/<int:recording_id>/summarize', methods=['POST'])
@login_required
def start_summarization(recording_id):
    """Queue summarization for a recording with optional custom prompt."""
    from src.services.job_queue import job_queue

    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user, require_edit=True):
        return jsonify({'error': 'Permission denied'}), 403

    # Check if transcription exists
    if not recording.transcription:
        return jsonify({'error': 'No transcription available - transcribe first'}), 400

    data = request.get_json() or {}

    params = {
        'custom_prompt': data.get('custom_prompt'),
        'user_id': current_user.id
    }

    # Queue the job
    job_id = job_queue.enqueue(
        user_id=current_user.id,
        recording_id=recording_id,
        job_type='reprocess_summary',
        params={k: v for k, v in params.items() if v is not None}
    )

    return jsonify({
        'success': True,
        'job_id': job_id,
        'status': 'QUEUED',
        'message': 'Summarization queued'
    })


# =============================================================================
# Chat with Recording
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>/chat', methods=['POST'])
@login_required
def chat_with_recording(recording_id):
    """Chat about a recording's content."""
    from src.services.llm import chat_client, call_chat_completion
    from src.tasks.processing import format_transcription_for_llm
    from src.models import SystemSetting

    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    if not recording.transcription:
        return jsonify({'error': 'No transcription available'}), 400

    data = request.get_json()
    if not data or not data.get('message'):
        return jsonify({'error': 'message is required'}), 400

    user_message = data['message']
    conversation_history = data.get('conversation_history', [])

    # Check if chat client is available
    if chat_client is None:
        return jsonify({'error': 'Chat service not available'}), 503

    # Format transcription
    formatted_transcription = format_transcription_for_llm(recording.transcription)

    # Get transcript limit
    transcript_limit = SystemSetting.get_setting('transcript_length_limit', 30000)
    if transcript_limit != -1:
        formatted_transcription = formatted_transcription[:transcript_limit]

    # Build system prompt
    system_prompt = f"""Tu es un assistant expert en analyse de transcriptions audio. Réponds de façon directe et professionnelle en français, en te basant sur la transcription ci-dessous.

**Enregistrement :** {recording.title}
**Participants :** {recording.participants or 'Non précisé'}

**Transcription :**
{formatted_transcription}

**Notes :** {recording.notes or 'Aucune note.'}
"""

    # Build messages
    messages = [{"role": "system", "content": system_prompt}]
    messages.extend(conversation_history)
    messages.append({"role": "user", "content": user_message})

    try:
        response = call_chat_completion(messages, user_id=current_user.id)

        return jsonify({
            'response': response,
            'sources': []  # Could be enhanced to extract relevant segments
        })
    except Exception as e:
        current_app.logger.error(f"Chat error: {e}")
        return jsonify({'error': 'Chat failed'}), 500


# =============================================================================
# Calendar Events
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>/events', methods=['GET'])
@login_required
def get_recording_events(recording_id):
    """Get calendar events extracted from a recording."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    events = Event.query.filter_by(recording_id=recording_id).all()

    return jsonify({
        'events': [{
            'id': e.id,
            'title': e.title,
            'start_datetime': e.start_datetime.isoformat() if e.start_datetime else None,
            'end_datetime': e.end_datetime.isoformat() if e.end_datetime else None,
            'description': e.description,
            'location': e.location
        } for e in events]
    })


@api_v1_bp.route('/recordings/<int:recording_id>/events/ics', methods=['GET'])
@login_required
def download_events_ics(recording_id):
    """Download all events as ICS file."""
    from src.api.events import generate_ics_content

    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    events = Event.query.filter_by(recording_id=recording_id).all()
    if not events:
        return jsonify({'error': 'No events found'}), 404

    # Generate combined ICS
    ics_lines = ['BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Speakr//Events//EN']

    for event in events:
        ics_lines.append('BEGIN:VEVENT')
        ics_lines.append(f'UID:{event.id}@speakr')
        ics_lines.append(f'SUMMARY:{event.title}')
        if event.start_datetime:
            ics_lines.append(f'DTSTART:{event.start_datetime.strftime("%Y%m%dT%H%M%S")}')
        if event.end_datetime:
            ics_lines.append(f'DTEND:{event.end_datetime.strftime("%Y%m%dT%H%M%S")}')
        if event.description:
            ics_lines.append(f'DESCRIPTION:{event.description}')
        if event.location:
            ics_lines.append(f'LOCATION:{event.location}')
        ics_lines.append('END:VEVENT')

    ics_lines.append('END:VCALENDAR')

    from flask import Response
    return Response(
        '\r\n'.join(ics_lines),
        mimetype='text/calendar',
        headers={'Content-Disposition': f'attachment; filename=events-{recording_id}.ics'}
    )


# =============================================================================
# Audio Download
# =============================================================================

@api_v1_bp.route('/recordings/<int:recording_id>/audio', methods=['GET'])
@login_required
def download_audio(recording_id):
    """Download or stream audio file."""
    recording = db.session.get(Recording, recording_id)
    if not recording:
        return jsonify({'error': 'Recording not found'}), 404

    if not has_recording_access(recording, current_user):
        return jsonify({'error': 'Permission denied'}), 403

    if recording.audio_deleted_at:
        return jsonify({'error': 'Audio has been deleted'}), 404

    if not recording.audio_path:
        return jsonify({'error': 'No audio file'}), 404

    audio_path = os.path.join(current_app.config.get('UPLOAD_FOLDER', 'uploads'), recording.audio_path)
    if not os.path.exists(audio_path):
        return jsonify({'error': 'Audio file not found'}), 404

    download = request.args.get('download', 'false').lower() == 'true'

    return send_file(
        audio_path,
        mimetype=recording.mime_type or 'audio/mpeg',
        as_attachment=download,
        download_name=recording.original_filename or f'recording-{recording_id}.mp3'
    )


# =============================================================================
# Batch Operations
# =============================================================================

@api_v1_bp.route('/recordings/batch', methods=['PATCH'])
@login_required
def batch_update_recordings():
    """Batch update multiple recordings."""
    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    recording_ids = data.get('recording_ids', [])
    updates = data.get('updates', {})

    if not recording_ids:
        return jsonify({'error': 'recording_ids required'}), 400

    results = []
    for recording_id in recording_ids:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            results.append({'id': recording_id, 'success': False, 'error': 'Not found'})
            continue

        if not has_recording_access(recording, current_user, require_edit=True):
            results.append({'id': recording_id, 'success': False, 'error': 'Permission denied'})
            continue

        try:
            if 'is_inbox' in updates:
                recording.is_inbox = bool(updates['is_inbox'])
            if 'is_highlighted' in updates:
                recording.is_highlighted = bool(updates['is_highlighted'])

            # Handle tag additions
            if 'add_tag_ids' in updates:
                for tag_id in updates['add_tag_ids']:
                    existing = RecordingTag.query.filter_by(
                        recording_id=recording_id,
                        tag_id=tag_id
                    ).first()
                    if not existing:
                        max_order = db.session.query(func.max(RecordingTag.order)).filter_by(
                            recording_id=recording_id
                        ).scalar() or 0
                        recording_tag = RecordingTag(
                            recording_id=recording_id,
                            tag_id=tag_id,
                            order=max_order + 1
                        )
                        db.session.add(recording_tag)

            # Handle tag removals
            if 'remove_tag_ids' in updates:
                for tag_id in updates['remove_tag_ids']:
                    RecordingTag.query.filter_by(
                        recording_id=recording_id,
                        tag_id=tag_id
                    ).delete()

            results.append({'id': recording_id, 'success': True})
        except Exception as e:
            results.append({'id': recording_id, 'success': False, 'error': str(e)})

    db.session.commit()

    success_count = sum(1 for r in results if r['success'])
    return jsonify({
        'success': True,
        'updated': success_count,
        'failed': len(results) - success_count,
        'results': results
    })


@api_v1_bp.route('/recordings/batch', methods=['DELETE'])
@login_required
def batch_delete_recordings():
    """Batch delete multiple recordings."""
    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    recording_ids = data.get('recording_ids', [])
    if not recording_ids:
        return jsonify({'error': 'recording_ids required'}), 400

    USERS_CAN_DELETE = os.environ.get('USERS_CAN_DELETE', 'true').lower() == 'true'
    if not USERS_CAN_DELETE and not current_user.is_admin:
        return jsonify({'error': 'Deletion not allowed'}), 403

    results = []
    for recording_id in recording_ids:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            results.append({'id': recording_id, 'success': False, 'error': 'Not found'})
            continue

        if recording.user_id != current_user.id and not current_user.is_admin:
            results.append({'id': recording_id, 'success': False, 'error': 'Permission denied'})
            continue

        try:
            # Delete audio file
            if recording.audio_path:
                audio_path = os.path.join(current_app.config.get('UPLOAD_FOLDER', 'uploads'), recording.audio_path)
                if os.path.exists(audio_path):
                    os.remove(audio_path)

            db.session.delete(recording)
            results.append({'id': recording_id, 'success': True})
        except Exception as e:
            results.append({'id': recording_id, 'success': False, 'error': str(e)})

    db.session.commit()

    success_count = sum(1 for r in results if r['success'])
    return jsonify({
        'success': True,
        'deleted': success_count,
        'failed': len(results) - success_count,
        'results': results
    })


@api_v1_bp.route('/recordings/batch/transcribe', methods=['POST'])
@login_required
def batch_transcribe_recordings():
    """Batch queue transcriptions for multiple recordings."""
    from src.services.job_queue import job_queue

    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    recording_ids = data.get('recording_ids', [])
    if not recording_ids:
        return jsonify({'error': 'recording_ids required'}), 400

    results = []
    for recording_id in recording_ids:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            results.append({'id': recording_id, 'success': False, 'error': 'Not found'})
            continue

        if not has_recording_access(recording, current_user, require_edit=True):
            results.append({'id': recording_id, 'success': False, 'error': 'Permission denied'})
            continue

        if recording.audio_deleted_at:
            results.append({'id': recording_id, 'success': False, 'error': 'Audio deleted'})
            continue

        try:
            job_id = job_queue.enqueue(
                user_id=current_user.id,
                recording_id=recording_id,
                job_type='reprocess_transcription',
                params={}
            )
            results.append({'id': recording_id, 'success': True, 'job_id': job_id})
        except Exception as e:
            results.append({'id': recording_id, 'success': False, 'error': str(e)})

    success_count = sum(1 for r in results if r['success'])
    return jsonify({
        'success': True,
        'queued': success_count,
        'failed': len(results) - success_count,
        'results': results
    })


# =============================================================================
# Settings
# =============================================================================

@api_v1_bp.route('/settings/auto-summarization', methods=['PUT'])
@login_required
def update_auto_summarization():
    """Toggle auto-summarization for the current user."""
    data = request.get_json()

    if data is None:
        return jsonify({'error': 'Invalid JSON'}), 400

    if 'enabled' not in data:
        return jsonify({'error': 'enabled field is required'}), 400

    current_user.auto_summarization = bool(data['enabled'])
    db.session.commit()

    return jsonify({
        'success': True,
        'auto_summarization': current_user.auto_summarization
    })


@api_v1_bp.route('/recordings/upload', methods=['POST'])
@login_required
def upload_recording():
    """
    Upload a recording and queue transcription (API).

    Multipart form-data fields:
      - file (required)
      - notes (optional)
      - file_last_modified (optional, ms epoch)
      - language (optional)
      - min_speakers (optional)
      - max_speakers (optional)
      - tag_ids[0], tag_ids[1], ... (optional)
      - tag_id (optional, legacy)
    """
    return _upload_file_ui()
