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

"""
Admin Dashboard - Phase 1

Web-based administration interface for multi-user Familiar deployments.
Built with Flask + HTMX for simplicity.

Features:
- User management (invite, edit roles, deactivate)
- Usage statistics
- Activity log viewer
- Skill configuration
- System settings

Run:
    python -m familiar.admin
"""

import json  # noqa: F401
import logging
import os
import secrets
from datetime import datetime, timedelta, timezone
from functools import wraps
from pathlib import Path  # noqa: F401

from flask import (
    Blueprint,
    Flask,
    Response,
    flash,
    g,
    jsonify,
    redirect,
    render_template,
    request,
    send_file,
    session,
    url_for,
)

logger = logging.getLogger(__name__)

# Create Flask app
app = Flask(__name__)
app.secret_key = os.environ.get("FAMILIAR_ADMIN_SECRET", secrets.token_hex(32))


@app.after_request
def _set_security_headers(response):
    """Add security headers to every response."""
    response.headers["Referrer-Policy"] = "no-referrer"
    return response


# Import after app creation to avoid circular imports
try:
    from familiar.core.data_store import DataCategory, DataScope, DataStore  # noqa: F401
    from familiar.core.users import (  # noqa: F401
        User,
        UserContext,
        UserRole,
        UserStatus,
        get_user_manager,
    )
except ImportError:
    # For standalone testing
    get_user_manager = None


# ============================================================
# LOGIN IP RATE LIMITING
# ============================================================

_login_ip_attempts: dict = {}  # ip -> {"count": int, "window_start": datetime}
_LOGIN_IP_MAX_PER_HOUR = 20


def _check_login_ip_rate_limit(ip: str) -> bool:
    """Return True if the IP is allowed to attempt login."""
    now = datetime.now(timezone.utc)
    record = _login_ip_attempts.get(ip)
    if not record:
        return True
    if now - record["window_start"] > timedelta(hours=1):
        _login_ip_attempts.pop(ip, None)
        return True
    return record["count"] < _LOGIN_IP_MAX_PER_HOUR


def _record_login_ip_attempt(ip: str):
    """Record a login attempt from this IP."""
    now = datetime.now(timezone.utc)
    record = _login_ip_attempts.get(ip)
    if not record or now - record["window_start"] > timedelta(hours=1):
        _login_ip_attempts[ip] = {"count": 1, "window_start": now}
    else:
        record["count"] += 1


# ============================================================
# INVITATION EMAIL
# ============================================================


def _send_invitation_email(to_email: str, invite_link: str, invited_by_email: str) -> bool:
    """Send an invitation email via SMTP. Returns True on success.

    Uses the same SMTP config as the email skill (FAMILIAR_EMAIL_ADDRESS,
    FAMILIAR_EMAIL_PASSWORD, FAMILIAR_SMTP_SERVER, FAMILIAR_SMTP_PORT).
    Falls back silently — the invite link is always shown in the admin UI.
    """
    import smtplib
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText

    address = os.environ.get("FAMILIAR_EMAIL_ADDRESS", "")
    password = os.environ.get("FAMILIAR_EMAIL_PASSWORD", "")
    smtp_server = os.environ.get("FAMILIAR_SMTP_SERVER", "")
    smtp_port = int(os.environ.get("FAMILIAR_SMTP_PORT", "587"))

    if not address or not password or not smtp_server:
        logger.info("Invitation email skipped — SMTP not configured")
        return False

    subject = "You've been invited to Familiar"
    body = (
        f"Hi,\n\n"
        f"{invited_by_email} has invited you to join Familiar.\n\n"
        f"Click the link below to accept your invitation:\n"
        f"{invite_link}\n\n"
        f"This link is single-use. You'll be asked to confirm your email address.\n\n"
        f"— Familiar"
    )

    try:
        msg = MIMEMultipart()
        msg["From"] = address
        msg["To"] = to_email
        msg["Subject"] = subject
        msg.attach(MIMEText(body, "plain"))

        with smtplib.SMTP(smtp_server, smtp_port, timeout=15) as server:
            server.starttls()
            server.login(address, password)
            server.send_message(msg)

        logger.info(f"Invitation email sent to {to_email}")
        return True
    except Exception as e:
        logger.warning(f"Failed to send invitation email to {to_email}: {e}")
        return False


# ============================================================
# AUTHENTICATION
# ============================================================


def get_current_user() -> User:
    """Get the currently logged-in user."""
    if "user_token" not in session:
        return None

    manager = get_user_manager()
    return manager.authenticate_session(session["user_token"])


def login_required(f):
    """Decorator to require login."""

    @wraps(f)
    def decorated(*args, **kwargs):
        user = get_current_user()
        if not user:
            flash("Please log in to continue", "warning")
            return redirect(url_for("admin.login"))
        g.user = user
        return f(*args, **kwargs)

    return decorated


def admin_required(f):
    """Decorator to require admin role."""

    @wraps(f)
    def decorated(*args, **kwargs):
        user = get_current_user()
        if not user:
            flash("Please log in to continue", "warning")
            return redirect(url_for("admin.login"))
        if user.role != UserRole.ADMIN:
            flash("Admin access required", "error")
            return redirect(url_for("admin.login"))
        g.user = user
        return f(*args, **kwargs)

    return decorated


# ============================================================
# ROUTES
# ============================================================

admin_bp = Blueprint("admin", __name__, template_folder="templates", static_folder="static")


@admin_bp.route("/")
@admin_required
def dashboard():
    """Main dashboard view."""
    manager = get_user_manager()

    stats = manager.get_stats()
    recent_activity = manager.get_audit_log(limit=10)

    # Phase 2: Get real usage stats from analytics
    usage_stats = {
        "messages_today": 0,
        "messages_week": 0,
        "api_cost_mtd": 0.00,
        "uptime_percent": 99.9,
    }

    try:
        from familiar.core.analytics import get_analytics_store

        store = get_analytics_store()

        # Get today's messages
        today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
        today_summary = store.get_summary(start=today_start)
        usage_stats["messages_today"] = today_summary.total_messages

        # Get this week's messages
        week_start = datetime.utcnow() - timedelta(days=7)
        week_summary = store.get_summary(start=week_start)
        usage_stats["messages_week"] = week_summary.total_messages

        # Get month-to-date cost
        month_start = datetime.utcnow().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
        month_summary = store.get_summary(start=month_start)
        usage_stats["api_cost_mtd"] = month_summary.total_cost_usd
    except ImportError:
        pass  # Analytics not available
    except Exception as e:
        logger.warning(f"Could not get analytics: {e}")

    return render_template(
        "dashboard.html", user=g.user, stats=stats, usage=usage_stats, activity=recent_activity
    )


# ---- Authentication Routes ----


@admin_bp.route("/login", methods=["GET", "POST"])
def login():
    """Login page with magic link."""
    if request.method == "POST":
        # IP-based rate limiting
        if not _check_login_ip_rate_limit(request.remote_addr):
            return Response("Too many login attempts. Please try again later.", status=429)
        _record_login_ip_attempt(request.remote_addr)

        email = request.form.get("email", "").strip().lower()

        if not email:
            flash("Email is required", "error")
            return render_template("login.html")

        manager = get_user_manager()
        user = manager.get_user_by_email(email)

        if not user or not user.is_active:
            # Generic message to prevent email enumeration
            flash("If that email is registered, a login link has been sent", "info")
            return render_template("login.html")

        # Create magic link (rate-limited per email in UserManager)
        try:
            token = manager.create_magic_link(email)
        except ValueError:
            # Per-email rate limit hit — show generic message to avoid enumeration
            flash("If that email is registered, a login link has been sent", "info")
            return render_template("login.html")

        link = url_for("admin.verify", token=token, _external=True)

        flash(f"Login link (dev mode): {link}", "info")
        logger.info(f"Magic link created for {email}: {link}")

        return render_template("login_sent.html", email=email)

    # Check if SSO is configured
    sso_enabled = bool(
        os.environ.get("GOOGLE_CLIENT_ID")
        or os.environ.get("MICROSOFT_CLIENT_ID")
        or os.environ.get("SSO_CLIENT_ID")
    )
    sso_provider = None
    if os.environ.get("GOOGLE_CLIENT_ID"):
        sso_provider = "google"
    elif os.environ.get("MICROSOFT_CLIENT_ID"):
        sso_provider = "microsoft"
    elif os.environ.get("SSO_CLIENT_ID"):
        sso_provider = "oidc"

    return render_template("login.html", sso_enabled=sso_enabled, sso_provider=sso_provider)


@admin_bp.route("/verify", methods=["GET", "POST"])
def verify():
    """Verify magic link token.

    GET  — render a landing page that auto-POSTs the token (keeps the
           token out of Referer headers, server access logs for the
           destination page, and browser history after redirect).
    POST — actually verify the token and create a session.
    """
    if request.method == "GET":
        token = request.args.get("token")
        if not token:
            flash("Invalid login link", "error")
            return redirect(url_for("admin.login"))
        return render_template("verify_landing.html", token=token)

    # POST: read token from form body (not query string)
    token = request.form.get("token")
    if not token:
        flash("Invalid login link", "error")
        return redirect(url_for("admin.login"))

    manager = get_user_manager()
    user = manager.verify_magic_link(token)

    if not user:
        flash("Invalid or expired login link", "error")
        return redirect(url_for("admin.login"))

    # Session regeneration: clear old session data before setting new token
    session.clear()

    # Create session
    session_token = manager.create_session(
        user, ip_address=request.remote_addr, user_agent=request.user_agent.string
    )

    session["user_token"] = session_token
    session.permanent = True

    flash(f"Welcome back, {user.display_name}!", "success")
    return redirect(url_for("admin.dashboard"))


@admin_bp.route("/logout")
def logout():
    """Log out and invalidate session."""
    if "user_token" in session:
        manager = get_user_manager()
        manager.invalidate_session(session["user_token"])
        del session["user_token"]

    flash("You have been logged out", "info")
    return redirect(url_for("admin.login"))


# ---- User Management Routes ----


@admin_bp.route("/users")
@admin_required
def users():
    """User list view."""
    manager = get_user_manager()

    users = manager.list_users()
    invitations = manager.list_pending_invitations()

    return render_template(
        "users.html", user=g.user, users=users, invitations=invitations, roles=UserRole
    )


@admin_bp.route("/users/invite", methods=["GET", "POST"])
@admin_required
def invite_user():
    """Invite a new user."""
    if request.method == "POST":
        email = request.form.get("email", "").strip().lower()
        role = request.form.get("role", "staff")

        if not email:
            flash("Email is required", "error")
            return render_template("invite.html", user=g.user, roles=UserRole)

        try:
            role_enum = UserRole(role)
        except ValueError:
            role_enum = UserRole.STAFF

        manager = get_user_manager()

        try:
            token = manager.create_invitation(email=email, role=role_enum, invited_by=g.user.id)

            # Generate invite link
            link = url_for("admin.accept_invite", token=token, _external=True)

            # Send the invitation email
            email_sent = _send_invitation_email(email, link, g.user.email)

            if email_sent:
                flash("Invitation sent!", "success")
            else:
                flash(
                    f"Invitation created (email not configured). Share this link: {link}", "success"
                )
            logger.info(f"Invitation created for {email} by {g.user.email} (emailed={email_sent})")

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

        except ValueError as e:
            flash(str(e), "error")

    return render_template("invite.html", user=g.user, roles=UserRole)


@admin_bp.route("/users/accept", methods=["GET", "POST"])
def accept_invite():
    """Accept an invitation via token link.

    GET  — show a confirmation page asking the user to type their email.
    POST — verify the submitted email matches the invitation, then proceed.
    """
    token = request.args.get("token") or request.form.get("token")

    if not token:
        flash("Invalid invitation link — no token provided.", "error")
        return redirect(url_for("admin.login"))

    manager = get_user_manager()

    # Validate the token against the invitations table
    invitation = manager.get_invitation_by_token(token)

    if not invitation:
        flash("This invitation link is invalid or has already been used.", "error")
        return redirect(url_for("admin.login"))

    if request.method == "GET":
        return render_template("accept_invite.html", token=token)

    # POST: require the user to confirm their email address
    submitted_email = request.form.get("email", "").strip().lower()
    invited_email = invitation["email"].strip().lower()

    if submitted_email != invited_email:
        flash("The email address you entered does not match this invitation.", "error")
        return render_template("accept_invite.html", token=token)

    email = invitation["email"]

    # If user already exists (e.g. re-invited), just log them in
    existing = manager.get_user_by_email(email)
    if existing and not existing.is_active:
        flash("Your account has been deactivated. Contact your administrator.", "error")
        return redirect(url_for("admin.login"))

    # Create a short-lived magic link for the invited email.
    # verify_magic_link() calls _accept_invitation() which creates the user
    # from the invitation row and marks the invitation used.
    magic_token = manager.create_magic_link(email, expires_minutes=30)
    verify_url = url_for("admin.verify", token=magic_token, _external=True)

    # In production this would be sent via email. For now redirect directly
    # since the person clicking the invite link is the invitee.
    logger.info(f"Invitation accepted for {email} — redirecting to verify")
    return redirect(verify_url)


@admin_bp.route("/users/<user_id>", methods=["GET", "POST"])
@admin_required
def edit_user(user_id):
    """Edit user details."""
    manager = get_user_manager()
    target_user = manager.get_user(user_id)

    if not target_user:
        flash("User not found", "error")
        return redirect(url_for("admin.users"))

    if request.method == "POST":
        action = request.form.get("action")

        if action == "update":
            name = request.form.get("name", "").strip()
            role = request.form.get("role")

            updates = {}
            if name:
                updates["name"] = name
            if role:
                try:
                    updates["role"] = UserRole(role)
                except ValueError:
                    pass

            if updates:
                manager.update_user(user_id, **updates)
                flash("User updated", "success")

        elif action == "deactivate":
            manager.deactivate_user(user_id)
            flash("User deactivated", "success")
            return redirect(url_for("admin.users"))

        elif action == "reactivate":
            manager.update_user(user_id, status=UserStatus.ACTIVE)
            flash("User reactivated", "success")

        elif action == "reset_sessions":
            manager.invalidate_all_sessions(user_id)
            flash("All sessions invalidated", "success")

        return redirect(url_for("admin.edit_user", user_id=user_id))

    # Get user's activity
    activity = manager.get_audit_log(user_id=user_id, limit=20)

    return render_template(
        "edit_user.html",
        user=g.user,
        target_user=target_user,
        activity=activity,
        roles=UserRole,
        statuses=UserStatus,
    )


# ---- Activity Log Routes ----


@admin_bp.route("/activity")
@admin_required
def activity():
    """Activity log view."""
    manager = get_user_manager()

    # Filters
    user_filter = request.args.get("user_id")
    action_filter = request.args.get("action")
    limit = int(request.args.get("limit", 100))

    logs = manager.get_audit_log(user_id=user_filter, action=action_filter, limit=limit)

    users = manager.list_users()

    return render_template("activity.html", user=g.user, logs=logs, users=users)


# ---- Settings Routes ----


@admin_bp.route("/settings", methods=["GET", "POST"])
@admin_required
def settings():
    """System settings view."""
    from familiar.core.config import load_config, save_config

    config = load_config()

    if request.method == "POST":
        # Update settings
        section = request.form.get("section")

        if section == "llm":
            config.llm.default_provider = request.form.get("provider", "anthropic")
            config.llm.model = request.form.get("model", "claude-sonnet-4-6")

        elif section == "security":
            config.security.daily_budget_usd = float(request.form.get("daily_budget", 2.0))
            config.security.session_timeout_minutes = int(request.form.get("session_timeout", 60))

        save_config(config)
        flash("Settings saved", "success")

    return render_template("settings.html", user=g.user, config=config)


# ---- API Endpoints (for HTMX) ----


@admin_bp.route("/api/stats")
@admin_required
def api_stats():
    """Get dashboard stats (HTMX endpoint)."""
    manager = get_user_manager()
    stats = manager.get_stats()
    return jsonify(stats)


@admin_bp.route("/api/activity")
@admin_required
def api_activity():
    """Get recent activity (HTMX endpoint)."""
    manager = get_user_manager()
    logs = manager.get_audit_log(limit=10)

    return render_template("_activity_list.html", logs=logs)


@admin_bp.route("/api/users/<user_id>/link-telegram", methods=["POST"])
@admin_required
def api_link_telegram(user_id):
    """Link Telegram account to user."""
    telegram_id = request.form.get("telegram_id")

    if not telegram_id:
        return jsonify({"error": "Telegram ID required"}), 400

    manager = get_user_manager()
    success = manager.link_telegram(user_id, int(telegram_id))

    if success:
        return jsonify({"success": True})
    else:
        return jsonify({"error": "Telegram ID already linked to another user"}), 400


# ============================================================
# PHASE 2: ANALYTICS ROUTES
# ============================================================


@admin_bp.route("/analytics")
@admin_required
def analytics():
    """Analytics dashboard."""
    from familiar.core.analytics import get_analytics_store

    store = get_analytics_store()

    # Get time range from query params
    days = int(request.args.get("days", 30))
    end = datetime.utcnow()
    start = end - timedelta(days=days)

    # Get summary
    summary = store.get_summary(start=start, end=end)

    # Get daily costs for chart
    daily_costs = store.get_daily_costs(start=start, end=end)

    # Get user rankings
    top_users = store.get_user_rankings(start=start, end=end, limit=10)

    return render_template(
        "analytics.html",
        user=g.user,
        summary=summary,
        daily_costs=daily_costs,
        top_users=top_users,
        days=days,
    )


@admin_bp.route("/analytics/export")
@admin_required
def analytics_export():
    """Export analytics to CSV."""
    from flask import Response

    from familiar.core.analytics import get_analytics_store

    store = get_analytics_store()

    # Get time range
    days = int(request.args.get("days", 30))
    end = datetime.utcnow()
    start = end - timedelta(days=days)

    user_id = request.args.get("user_id")

    csv_data = store.export_csv(start=start, end=end, user_id=user_id)

    filename = f"familiar_analytics_{start.strftime('%Y%m%d')}_{end.strftime('%Y%m%d')}.csv"

    return Response(
        csv_data,
        mimetype="text/csv",
        headers={"Content-Disposition": f"attachment;filename={filename}"},
    )


@admin_bp.route("/analytics/api/daily-costs")
@admin_required
def api_daily_costs():
    """Get daily costs for chart (JSON)."""
    from familiar.core.analytics import get_analytics_store

    store = get_analytics_store()

    days = int(request.args.get("days", 30))
    end = datetime.utcnow()
    start = end - timedelta(days=days)

    user_id = request.args.get("user_id")

    daily_costs = store.get_daily_costs(start=start, end=end, user_id=user_id)

    return jsonify({"labels": [d[0] for d in daily_costs], "values": [d[1] for d in daily_costs]})


@admin_bp.route("/analytics/api/user-rankings")
@admin_required
def api_user_rankings():
    """Get user rankings for chart (JSON)."""
    from familiar.core.analytics import get_analytics_store

    store = get_analytics_store()

    days = int(request.args.get("days", 30))
    end = datetime.utcnow()
    start = end - timedelta(days=days)

    rankings = store.get_user_rankings(start=start, end=end, limit=10)

    return jsonify(rankings)


@admin_bp.route("/budgets")
@admin_required
def budgets():
    """Budget management page."""
    from familiar.core.analytics import BudgetAlert, get_analytics_store

    store = get_analytics_store()

    # Get all alerts
    with store._connect() as conn:
        rows = conn.execute("SELECT * FROM budget_alerts ORDER BY name").fetchall()

    alerts = [
        BudgetAlert(
            id=row["id"],
            name=row["name"],
            user_id=row["user_id"],
            monthly_budget_usd=row["monthly_budget_usd"],
            alert_threshold_pct=row["alert_threshold_pct"],
            notify_email=row["notify_email"],
            enabled=row["enabled"],
        )
        for row in rows
    ]

    # Check current status
    triggered = store.check_alerts()

    return render_template("budgets.html", user=g.user, alerts=alerts, triggered=triggered)


@admin_bp.route("/budgets/create", methods=["GET", "POST"])
@admin_required
def create_budget():
    """Create budget alert."""
    from familiar.core.analytics import BudgetAlert, get_analytics_store

    if request.method == "POST":
        store = get_analytics_store()

        alert = BudgetAlert(
            name=request.form.get("name"),
            user_id=request.form.get("user_id") or None,
            monthly_budget_usd=float(request.form.get("budget", 100)),
            alert_threshold_pct=float(request.form.get("threshold", 80)),
            notify_email=request.form.get("notify_email"),
        )

        store.create_alert(alert)
        flash(f"Budget alert '{alert.name}' created", "success")
        return redirect(url_for("admin.budgets"))

    manager = get_user_manager()
    users = manager.list_users()

    return render_template("create_budget.html", user=g.user, users=users)


# ============================================================
# PHASE 4: AUDIT, HA, BACKUP ROUTES
# ============================================================


@admin_bp.route("/audit")
@admin_required
def audit():
    """Audit log viewer."""
    from familiar.core.audit import AuditAction, get_audit_store

    store = get_audit_store()

    days = int(request.args.get("days", 7))
    action_filter = request.args.get("action")
    actor_filter = request.args.get("actor")

    start = datetime.utcnow() - timedelta(days=days)

    events = store.query(
        start=start,
        action=AuditAction(action_filter) if action_filter else None,
        actor_email=actor_filter if actor_filter else None,
        limit=500,
    )

    stats = store.get_stats(start=start)

    return render_template(
        "audit.html",
        user=g.user,
        events=events,
        stats=stats,
        days=days,
        actions=[a.value for a in AuditAction],
    )


@admin_bp.route("/audit/export")
@admin_required
def audit_export():
    """Export audit logs."""
    from familiar.core.audit import AuditAction, audit_log, get_audit_store

    store = get_audit_store()

    format = request.args.get("format", "csv")
    days = int(request.args.get("days", 30))

    start = datetime.utcnow() - timedelta(days=days)
    end = datetime.utcnow()

    # Log the export
    audit_log(
        action=AuditAction.AUDIT_EXPORT,
        actor_id=g.user.id if g.user else None,
        actor_email=g.user.email if g.user else None,
        details={"format": format, "days": days},
    )

    if format == "json":
        data = store.export_json(start=start, end=end)
        mimetype = "application/json"
        filename = f"audit_export_{datetime.utcnow().strftime('%Y%m%d')}.json"
    elif format == "siem":
        data = store.export_siem(start=start, end=end)
        mimetype = "application/x-ndjson"
        filename = f"audit_export_{datetime.utcnow().strftime('%Y%m%d')}.ndjson"
    else:
        data = store.export_csv(start=start, end=end)
        mimetype = "text/csv"
        filename = f"audit_export_{datetime.utcnow().strftime('%Y%m%d')}.csv"

    return Response(
        data, mimetype=mimetype, headers={"Content-Disposition": f"attachment; filename={filename}"}
    )


@admin_bp.route("/audit/verify", methods=["POST"])
@admin_required
def audit_verify():
    """Verify audit hash chain."""
    from familiar.core.audit import get_audit_store

    store = get_audit_store()
    result = store.verify_chain()
    return jsonify(result)


@admin_bp.route("/system")
@admin_required
def system_status():
    """System status and HA page."""
    from familiar.core.backup import BackupManager
    from familiar.core.ha import (  # noqa: F401
        HAConfig,
        NodeState,
        get_ha_manager,
        get_health_checker,
    )

    # Health check
    health_checker = get_health_checker()
    ha_manager = get_ha_manager()
    health = health_checker.check(ha_manager)

    # HA status
    ha_enabled = ha_manager is not None and ha_manager.config.enabled if ha_manager else False

    context = {
        "user": g.user,
        "health": health,
        "ha_enabled": ha_enabled,
        "ha_mode": ha_manager.config.mode.value if ha_manager else "standalone",
        "node_state": ha_manager.state.value if ha_manager else "active",
        "current_node": ha_manager.node_id if ha_manager else None,
        "leader": ha_manager.get_leader() if ha_manager else None,
        "nodes": ha_manager.get_nodes() if ha_manager else [],
        "cluster_size": len(ha_manager.get_nodes()) if ha_manager else 1,
        "active_nodes": len([n for n in ha_manager.get_nodes() if n.state == NodeState.ACTIVE])
        if ha_manager
        else 1,
        "backups": [],
    }

    # Get recent backups
    try:
        backup_mgr = BackupManager()
        context["backups"] = backup_mgr.list_backups()[:5]
    except Exception as e:
        logger.warning(f"Could not list backups: {e}")

    return render_template("system_status.html", **context)


@admin_bp.route("/backup/create", methods=["GET", "POST"])
@admin_required
def backup_create():
    """Create a backup."""
    from familiar.core.backup import BackupManager, BackupType

    if request.method == "POST":
        backup_type = request.form.get("type", "full")
        type_map = {
            "full": BackupType.FULL,
            "config": BackupType.CONFIG_ONLY,
            "data": BackupType.DATA_ONLY,
        }

        try:
            manager = BackupManager()
            manifest = manager.create_backup(backup_type=type_map.get(backup_type, BackupType.FULL))
            flash(f"Backup created: {manifest.id}", "success")
        except Exception as e:
            flash(f"Backup failed: {str(e)}", "error")

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

    return render_template("create_backup.html", user=g.user)


@admin_bp.route("/backup/<backup_id>/download")
@admin_required
def backup_download(backup_id: str):
    """Download a backup."""
    from familiar.core.backup import BackupManager

    manager = BackupManager()
    manifest = manager.get_backup(backup_id)

    if not manifest:
        flash("Backup not found", "error")
        return redirect(url_for("admin.system_status"))

    archive_path = manager.config.path / f"familiar_backup_{backup_id}.tar.gz"

    if not archive_path.exists():
        flash("Backup file not found", "error")
        return redirect(url_for("admin.system_status"))

    return send_file(
        archive_path, as_attachment=True, download_name=f"familiar_backup_{backup_id}.tar.gz"
    )


@admin_bp.route("/backup/<backup_id>/restore", methods=["POST"])
@admin_required
def backup_restore(backup_id: str):
    """Restore from a backup."""
    from familiar.core.backup import BackupManager

    try:
        manager = BackupManager()
        result = manager.restore(backup_id)
        return jsonify(result)
    except Exception as e:
        return jsonify({"error": str(e)}), 500


# ============================================================
# PHASE 2: SSO ROUTES
# ============================================================


@admin_bp.route("/auth/sso")
def sso_login():
    """Redirect to SSO provider."""
    try:
        from familiar.core.auth import create_sso_client

        # Check if SSO is configured
        client_id = os.environ.get("GOOGLE_CLIENT_ID") or os.environ.get("MICROSOFT_CLIENT_ID")
        if not client_id:
            flash("SSO not configured. Use magic link login.", "warning")
            return redirect(url_for("admin.login"))

        client = create_sso_client()
        auth_url, state = client.get_authorization_url()
        session["sso_state"] = state

        return redirect(auth_url)
    except Exception as e:
        logger.error(f"SSO login error: {e}")
        flash(f"SSO error: {str(e)}", "error")
        return redirect(url_for("admin.login"))


@admin_bp.route("/auth/callback")
def sso_callback():
    """Handle SSO callback."""
    import asyncio

    try:
        from familiar.core.auth import create_sso_client

        # Check for errors
        error = request.args.get("error")
        if error:
            flash(f"SSO error: {error}", "error")
            return redirect(url_for("admin.login"))

        code = request.args.get("code")
        state = request.args.get("state")

        client = create_sso_client()

        if not client.verify_state(state):
            flash("Invalid or expired login session", "error")
            return redirect(url_for("admin.login"))

        # Exchange code for tokens
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        tokens = loop.run_until_complete(client.exchange_code(code, state))
        sso_user = loop.run_until_complete(client.get_user_info(tokens))

        loop.close()

        # Validate domain
        if not client.validate_domain(sso_user):
            flash(f"Email domain not allowed: {sso_user.domain}", "error")
            return redirect(url_for("admin.login"))

        # Provision user
        manager = get_user_manager()
        user = client.provision_user(sso_user, manager)

        # Session regeneration: clear old session data to prevent fixation
        session.clear()

        # Create session
        session_obj = manager.create_session(user.id)
        session["user_token"] = session_obj.token

        flash(f"Welcome, {user.name}!", "success")
        return redirect(url_for("admin.dashboard"))

    except Exception as e:
        logger.error(f"SSO callback error: {e}")
        flash(f"Login failed: {str(e)}", "error")
        return redirect(url_for("admin.login"))


# ============================================================
# REGISTER BLUEPRINT
# ============================================================

app.register_blueprint(admin_bp, url_prefix="/admin")


# Redirect root to admin
@app.route("/")
def index():
    return redirect(url_for("admin.dashboard"))


# ============================================================
# MAIN
# ============================================================


def run_admin(host: str = "0.0.0.0", port: int = 5001, debug: bool = False):
    """Run the admin dashboard server."""
    logger.info(f"Starting admin dashboard on http://{host}:{port}")
    app.run(host=host, port=port, debug=debug)


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Familiar Admin Dashboard")
    parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
    parser.add_argument("--port", type=int, default=5001, help="Port to bind to")
    parser.add_argument("--debug", action="store_true", help="Enable debug mode")

    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)

    run_admin(host=args.host, port=args.port, debug=args.debug)
