"""
Admin routes for StarPunk

Handles authenticated admin functionality including dashboard, note creation,
editing, and deletion. All routes require authentication.
"""

from flask import Blueprint, flash, g, jsonify, redirect, render_template, request, url_for
import os
import shutil
from datetime import datetime

from starpunk.auth import require_auth
from starpunk.notes import (
    create_note,
    delete_note,
    list_notes,
    get_note,
    update_note,
)

# Create blueprint
bp = Blueprint("admin", __name__, url_prefix="/admin")


@bp.route("/")
@require_auth
def dashboard():
    """
    Admin dashboard with note list

    Displays all notes (published and drafts) with management controls.
    Requires authentication.

    Returns:
        Rendered dashboard template with complete note list

    Decorator: @require_auth
    Template: templates/admin/dashboard.html
    Access: g.user_me (set by require_auth decorator)
    """
    # Get all notes (published and drafts)
    notes = list_notes()

    return render_template("admin/dashboard.html", notes=notes, user_me=g.me)


@bp.route("/new", methods=["GET"])
@require_auth
def new_note_form():
    """
    Display create note form

    Shows empty form for creating a new note.
    Requires authentication.

    Returns:
        Rendered new note form template

    Decorator: @require_auth
    Template: templates/admin/new.html
    """
    return render_template("admin/new.html")


@bp.route("/new", methods=["POST"])
@require_auth
def create_note_submit():
    """
    Handle new note submission

    Creates a new note from submitted form data.
    Requires authentication.

    Form data:
        content: Markdown content (required)
        published: Checkbox for published status (optional)
        custom_slug: Optional custom slug (v1.2.0 Phase 1)
        media_files: Multiple file upload (v1.2.0 Phase 3)
        captions[]: Captions for each media file (v1.2.0 Phase 3)
        tags: Comma-separated tag list (v1.3.0 Phase 3)

    Returns:
        Redirect to dashboard on success, back to form on error

    Decorator: @require_auth
    """
    from starpunk.media import save_media, attach_media_to_note
    from starpunk.tags import parse_tag_input

    content = request.form.get("content", "").strip()
    published = "published" in request.form
    custom_slug = request.form.get("custom_slug", "").strip()
    tags_input = request.form.get("tags", "")

    if not content:
        flash("Content cannot be empty", "error")
        return redirect(url_for("admin.new_note_form"))

    # Parse tags (v1.3.0 Phase 3)
    tags = parse_tag_input(tags_input)

    try:
        # Create note first (per Q4)
        note = create_note(
            content,
            published=published,
            custom_slug=custom_slug if custom_slug else None,
            tags=tags if tags else None
        )

        # Handle media uploads (v1.2.0 Phase 3)
        media_files = request.files.getlist('media_files')
        captions = request.form.getlist('captions[]')

        if media_files and any(f.filename for f in media_files):
            # Per Q35: Accept valid, reject invalid (not atomic)
            media_ids = []
            errors = []

            for i, file in enumerate(media_files):
                if not file.filename:
                    continue

                try:
                    # Read file data
                    file_data = file.read()

                    # Save and optimize media
                    media_info = save_media(file_data, file.filename)
                    media_ids.append(media_info['id'])
                except ValueError as e:
                    errors.append(f"{file.filename}: {str(e)}")
                except Exception as e:
                    errors.append(f"{file.filename}: Upload failed")

            if media_ids:
                # Ensure captions list matches media_ids length
                while len(captions) < len(media_ids):
                    captions.append('')

                # Attach media to note
                attach_media_to_note(note.id, media_ids, captions[:len(media_ids)])

            if errors:
                flash(f"Note created, but some images failed: {'; '.join(errors)}", "warning")

        flash(f"Note created: {note.slug}", "success")
        return redirect(url_for("admin.dashboard"))

    except ValueError as e:
        flash(f"Error creating note: {e}", "error")
        return redirect(url_for("admin.new_note_form"))
    except Exception as e:
        flash(f"Unexpected error creating note: {e}", "error")
        return redirect(url_for("admin.new_note_form"))


@bp.route("/edit/<int:note_id>", methods=["GET"])
@require_auth
def edit_note_form(note_id: int):
    """
    Display edit note form

    Shows form pre-filled with existing note content for editing.
    Requires authentication.

    Args:
        note_id: Database ID of note to edit

    Returns:
        Rendered edit form template or 404 if note not found

    Decorator: @require_auth
    Template: templates/admin/edit.html
    """
    from starpunk.tags import get_note_tags

    note = get_note(id=note_id)

    if not note:
        flash("Note not found", "error")
        return redirect(url_for("admin.dashboard")), 404

    # Pre-load tags for the edit form (v1.3.0 Phase 3)
    tags = get_note_tags(note.id)
    object.__setattr__(note, '_cached_tags', tags)

    return render_template("admin/edit.html", note=note)


@bp.route("/edit/<int:note_id>", methods=["POST"])
@require_auth
def update_note_submit(note_id: int):
    """
    Handle note update submission

    Updates existing note with submitted form data.
    Requires authentication.

    Args:
        note_id: Database ID of note to update

    Form data:
        content: Updated markdown content (required)
        published: Checkbox for published status (optional)
        tags: Comma-separated tag list (v1.3.0 Phase 3)

    Returns:
        Redirect to dashboard on success, back to form on error

    Decorator: @require_auth
    """
    from starpunk.tags import parse_tag_input

    # Check if note exists first
    existing_note = get_note(id=note_id, load_content=False)
    if not existing_note:
        flash("Note not found", "error")
        return redirect(url_for("admin.dashboard")), 404

    content = request.form.get("content", "").strip()
    published = "published" in request.form
    tags_input = request.form.get("tags", "")

    if not content:
        flash("Content cannot be empty", "error")
        return redirect(url_for("admin.edit_note_form", note_id=note_id))

    # Parse tags (v1.3.0 Phase 3)
    tags = parse_tag_input(tags_input)

    try:
        note = update_note(
            id=note_id,
            content=content,
            published=published,
            tags=tags if tags else None
        )
        flash(f"Note updated: {note.slug}", "success")
        return redirect(url_for("admin.dashboard"))
    except ValueError as e:
        flash(f"Error updating note: {e}", "error")
        return redirect(url_for("admin.edit_note_form", note_id=note_id))
    except Exception as e:
        flash(f"Unexpected error updating note: {e}", "error")
        return redirect(url_for("admin.edit_note_form", note_id=note_id))


@bp.route("/delete/<int:note_id>", methods=["POST"])
@require_auth
def delete_note_submit(note_id: int):
    """
    Handle note deletion

    Deletes a note after confirmation.
    Requires authentication.

    Args:
        note_id: Database ID of note to delete

    Form data:
        confirm: Must be 'yes' to proceed with deletion

    Returns:
        Redirect to dashboard with success/error message

    Decorator: @require_auth
    """
    # Check if note exists first (per ADR-012)
    existing_note = get_note(id=note_id, load_content=False)
    if not existing_note:
        flash("Note not found", "error")
        return redirect(url_for("admin.dashboard")), 404

    # Check for confirmation
    if request.form.get("confirm") != "yes":
        flash("Deletion cancelled", "info")
        return redirect(url_for("admin.dashboard"))

    try:
        delete_note(id=note_id, soft=False)
        flash("Note deleted successfully", "success")
    except ValueError as e:
        flash(f"Error deleting note: {e}", "error")
    except Exception as e:
        flash(f"Unexpected error deleting note: {e}", "error")

    return redirect(url_for("admin.dashboard"))


def transform_metrics_for_template(metrics_stats):
    """
    Transform metrics stats to match template structure

    The template expects direct access to metrics.database.count, but
    get_metrics_stats() returns metrics.by_type.database.count.
    This function adapts the data structure to match template expectations.

    Args:
        metrics_stats: Dict from get_metrics_stats() with nested by_type structure

    Returns:
        Dict with flattened structure matching template expectations

    Per ADR-060: Route Adapter Pattern for template compatibility
    """
    transformed = {}

    # Map by_type to direct access
    for op_type in ['database', 'http', 'render']:
        if 'by_type' in metrics_stats and op_type in metrics_stats['by_type']:
            type_data = metrics_stats['by_type'][op_type]
            transformed[op_type] = {
                'count': type_data.get('count', 0),
                'avg': type_data.get('avg_duration_ms', 0),
                'min': type_data.get('min_duration_ms', 0),
                'max': type_data.get('max_duration_ms', 0)
            }
        else:
            # Provide defaults for missing types or when by_type doesn't exist
            transformed[op_type] = {
                'count': 0,
                'avg': 0,
                'min': 0,
                'max': 0
            }

    # Keep other top-level stats
    transformed['total_count'] = metrics_stats.get('total_count', 0)
    transformed['max_size'] = metrics_stats.get('max_size', 1000)
    transformed['process_id'] = metrics_stats.get('process_id', 0)

    return transformed


@bp.route("/metrics-dashboard")
@require_auth
def metrics_dashboard():
    """
    Metrics visualization dashboard (Phase 3)

    Displays performance metrics, database statistics, feed statistics,
    and system health with visual charts and auto-refresh capability.

    Per Q19 requirements:
    - Server-side rendering with Jinja2
    - htmx for auto-refresh
    - Chart.js from CDN for graphs
    - Progressive enhancement (works without JS)

    Per v1.1.2 Phase 3:
    - Feed statistics by format
    - Cache hit/miss rates
    - Format popularity breakdown

    Returns:
        Rendered dashboard template with metrics

    Decorator: @require_auth
    Template: templates/admin/metrics_dashboard.html
    """
    # Defensive imports with graceful degradation for missing modules
    try:
        from starpunk.database.pool import get_pool_stats
        from starpunk.monitoring import get_metrics_stats
        from starpunk.monitoring.business import get_feed_statistics
        monitoring_available = True
    except ImportError:
        monitoring_available = False
        # Provide fallback functions that return error messages
        def get_pool_stats():
            return {"error": "Database pool monitoring not available"}
        def get_metrics_stats():
            return {"error": "Monitoring module not implemented"}
        def get_feed_statistics():
            return {"error": "Feed statistics not available"}

    # Get current metrics for initial page load
    metrics_data = {}
    pool_stats = {}
    feed_stats = {}

    try:
        raw_metrics = get_metrics_stats()
        metrics_data = transform_metrics_for_template(raw_metrics)
    except Exception as e:
        flash(f"Error loading metrics: {e}", "warning")
        # Provide safe defaults matching template expectations
        metrics_data = {
            'database': {'count': 0, 'avg': 0, 'min': 0, 'max': 0},
            'http': {'count': 0, 'avg': 0, 'min': 0, 'max': 0},
            'render': {'count': 0, 'avg': 0, 'min': 0, 'max': 0},
            'total_count': 0,
            'max_size': 1000,
            'process_id': 0
        }

    try:
        pool_stats = get_pool_stats()
    except Exception as e:
        flash(f"Error loading pool stats: {e}", "warning")

    try:
        feed_stats = get_feed_statistics()
    except Exception as e:
        flash(f"Error loading feed stats: {e}", "warning")
        # Provide safe defaults
        feed_stats = {
            'by_format': {
                'rss': {'generated': 0, 'cached': 0, 'total': 0, 'avg_duration_ms': 0.0},
                'atom': {'generated': 0, 'cached': 0, 'total': 0, 'avg_duration_ms': 0.0},
                'json': {'generated': 0, 'cached': 0, 'total': 0, 'avg_duration_ms': 0.0},
            },
            'cache': {'hits': 0, 'misses': 0, 'hit_rate': 0.0, 'entries': 0, 'evictions': 0},
            'total_requests': 0,
            'format_percentages': {'rss': 0.0, 'atom': 0.0, 'json': 0.0},
        }

    return render_template(
        "admin/metrics_dashboard.html",
        metrics=metrics_data,
        pool=pool_stats,
        feeds=feed_stats,
        user_me=g.me
    )


@bp.route("/metrics")
@require_auth
def metrics():
    """
    Performance metrics and database pool statistics endpoint

    Per Phase 2 requirements:
    - Expose database pool statistics
    - Show performance metrics from MetricsBuffer
    - Requires authentication

    Per v1.1.2 Phase 3:
    - Include feed statistics

    Returns:
        JSON with metrics, pool statistics, and feed statistics

    Response codes:
        200: Metrics retrieved successfully

    Decorator: @require_auth
    """
    from flask import current_app
    from starpunk.database.pool import get_pool_stats
    from starpunk.monitoring import get_metrics_stats
    from starpunk.monitoring.business import get_feed_statistics

    response = {
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "process_id": os.getpid(),
        "database": {},
        "performance": {},
        "feeds": {}
    }

    # Get database pool statistics
    try:
        pool_stats = get_pool_stats()
        response["database"]["pool"] = pool_stats
    except Exception as e:
        response["database"]["pool"] = {"error": str(e)}

    # Get performance metrics
    try:
        metrics_stats = get_metrics_stats()
        response["performance"] = metrics_stats
    except Exception as e:
        response["performance"] = {"error": str(e)}

    # Get feed statistics
    try:
        feed_stats = get_feed_statistics()
        response["feeds"] = feed_stats
    except Exception as e:
        response["feeds"] = {"error": str(e)}

    return jsonify(response), 200


@bp.route("/health")
@require_auth
def health_diagnostics():
    """
    Full health diagnostics endpoint for admin use

    Per developer Q&A Q10:
    - Always requires authentication
    - Provides comprehensive diagnostics
    - Includes metrics, database pool statistics, and system info

    Returns:
        JSON with complete system diagnostics

    Response codes:
        200: Diagnostics retrieved successfully
        500: Critical health issues detected

    Decorator: @require_auth
    """
    from flask import current_app
    from starpunk.database.pool import get_pool_stats

    diagnostics = {
        "status": "healthy",
        "version": current_app.config.get("VERSION", "unknown"),
        "environment": current_app.config.get("ENV", "unknown"),
        "process_id": os.getpid(),
        "checks": {},
        "metrics": {},
        "database": {}
    }

    overall_healthy = True

    # Database connectivity check
    try:
        from starpunk.database import get_db
        db = get_db()
        result = db.execute("SELECT 1").fetchone()
        db.close()
        diagnostics["checks"]["database"] = {
            "status": "healthy",
            "message": "Database accessible"
        }

        # Get database pool statistics
        try:
            pool_stats = get_pool_stats()
            diagnostics["database"]["pool"] = pool_stats
        except Exception as e:
            diagnostics["database"]["pool"] = {"error": str(e)}

    except Exception as e:
        diagnostics["checks"]["database"] = {
            "status": "unhealthy",
            "error": str(e)
        }
        overall_healthy = False

    # Filesystem check
    try:
        data_path = current_app.config.get("DATA_PATH", "data")
        if not os.path.exists(data_path):
            raise Exception("Data path not accessible")

        diagnostics["checks"]["filesystem"] = {
            "status": "healthy",
            "path": data_path,
            "writable": os.access(data_path, os.W_OK),
            "readable": os.access(data_path, os.R_OK)
        }
    except Exception as e:
        diagnostics["checks"]["filesystem"] = {
            "status": "unhealthy",
            "error": str(e)
        }
        overall_healthy = False

    # Disk space check
    try:
        data_path = current_app.config.get("DATA_PATH", "data")
        stat = shutil.disk_usage(data_path)
        percent_free = (stat.free / stat.total) * 100

        diagnostics["checks"]["disk"] = {
            "status": "healthy" if percent_free > 10 else ("warning" if percent_free > 5 else "critical"),
            "total_gb": round(stat.total / (1024**3), 2),
            "used_gb": round(stat.used / (1024**3), 2),
            "free_gb": round(stat.free / (1024**3), 2),
            "percent_free": round(percent_free, 2),
            "percent_used": round((stat.used / stat.total) * 100, 2)
        }

        if percent_free <= 5:
            overall_healthy = False
    except Exception as e:
        diagnostics["checks"]["disk"] = {
            "status": "unhealthy",
            "error": str(e)
        }
        overall_healthy = False

    # Performance metrics
    try:
        from starpunk.monitoring import get_metrics_stats
        metrics_stats = get_metrics_stats()
        diagnostics["metrics"] = metrics_stats
    except Exception as e:
        diagnostics["metrics"] = {"error": str(e)}

    # Update overall status
    diagnostics["status"] = "healthy" if overall_healthy else "unhealthy"

    return jsonify(diagnostics), 200 if overall_healthy else 500
