"""
Platform admin console routes.

All routes require platform_admin role. Access by any other authenticated
user returns 403.
"""

from __future__ import annotations

import datetime as dt
import json

from flask import render_template, redirect, url_for, request, jsonify, abort, flash
from flask_login import current_user, login_required
from sqlalchemy import func, text

from . import admin_bp
from ..db import session_scope, engine
from ..public_models import Organization, User, PublicAuditLog


def _require_platform_admin():
    """Abort with 403 if the current user is not a platform admin."""
    if not current_user.is_authenticated or current_user.role != "platform_admin":
        abort(403)


# ── Tenant list ───────────────────────────────────────────────────────────────

@admin_bp.route("/")
@login_required
def index():
    _require_platform_admin()
    with session_scope() as session:
        orgs = (
            session.query(Organization)
            .order_by(Organization.created_at.desc())
            .all()
        )
        org_list = [
            {
                "id": o.id,
                "name": o.name,
                "slug": o.slug,
                "plan": o.plan,
                "schema_name": o.schema_name,
                "is_suspended": o.is_suspended,
                "onboarding_complete": o.onboarding_complete,
                "created_at": o.created_at,
                "user_count": len(o.users),
            }
            for o in orgs
        ]
    return render_template("admin/index.html", orgs=org_list)


# ── Org detail ────────────────────────────────────────────────────────────────

@admin_bp.route("/org/<int:org_id>")
@login_required
def org_detail(org_id: int):
    _require_platform_admin()
    with session_scope() as session:
        org = session.query(Organization).filter_by(id=org_id).one_or_none()
        if not org:
            abort(404)

        users = session.query(User).filter_by(org_id=org_id).all()

        # Recent audit events for this org
        audit = (
            session.query(PublicAuditLog)
            .filter_by(org_id=org_id)
            .order_by(PublicAuditLog.created_at.desc())
            .limit(20)
            .all()
        )

        # AI usage from tenant schema
        ai_usage = _get_tenant_ai_usage(org.schema_name)

        data = {
            "id": org.id,
            "name": org.name,
            "slug": org.slug,
            "plan": org.plan,
            "schema_name": org.schema_name,
            "is_suspended": org.is_suspended,
            "suspended_reason": org.suspended_reason,
            "onboarding_complete": org.onboarding_complete,
            "ai_credits_used": org.ai_credits_used,
            "ai_credits_reset_at": org.ai_credits_reset_at,
            "stripe_customer_id": org.stripe_customer_id,
            "stripe_subscription_id": org.stripe_subscription_id,
            "has_byok_key": bool(org.openai_api_key_encrypted),
            "created_at": org.created_at,
            "users": [{"id": u.id, "email": u.email, "role": u.role, "last_login_at": u.last_login_at} for u in users],
            "recent_audit": [{"action": a.action, "created_at": a.created_at, "ip": a.ip_address} for a in audit],
            "ai_usage": ai_usage,
        }
    return render_template("admin/org_detail.html", org=data)


# ── Admin actions ─────────────────────────────────────────────────────────────

@admin_bp.route("/org/<int:org_id>/suspend", methods=["POST"])
@login_required
def suspend_org(org_id: int):
    _require_platform_admin()
    reason = request.form.get("reason", "Suspended by platform admin")
    with session_scope() as session:
        org = session.query(Organization).filter_by(id=org_id).one_or_none()
        if not org:
            abort(404)
        org.is_suspended = True
        org.suspended_reason = reason
        _log_admin_action(session, org_id, "org_suspended", {"reason": reason})
    flash(f"Organisation {org_id} suspended.", "warning")
    return redirect(url_for("admin.org_detail", org_id=org_id))


@admin_bp.route("/org/<int:org_id>/unsuspend", methods=["POST"])
@login_required
def unsuspend_org(org_id: int):
    _require_platform_admin()
    with session_scope() as session:
        org = session.query(Organization).filter_by(id=org_id).one_or_none()
        if not org:
            abort(404)
        org.is_suspended = False
        org.suspended_reason = None
        _log_admin_action(session, org_id, "org_unsuspended", {})
    flash(f"Organisation {org_id} unsuspended.", "success")
    return redirect(url_for("admin.org_detail", org_id=org_id))


@admin_bp.route("/org/<int:org_id>/change-plan", methods=["POST"])
@login_required
def change_plan(org_id: int):
    _require_platform_admin()
    new_plan = request.form.get("plan", "free")
    if new_plan not in ("internal", "free", "basic", "pro"):
        abort(400)
    with session_scope() as session:
        org = session.query(Organization).filter_by(id=org_id).one_or_none()
        if not org:
            abort(404)
        old_plan = org.plan
        org.plan = new_plan
        _log_admin_action(session, org_id, "plan_changed", {"from": old_plan, "to": new_plan})
    flash(f"Plan changed to {new_plan}.", "success")
    return redirect(url_for("admin.org_detail", org_id=org_id))


@admin_bp.route("/org/<int:org_id>/add-credits", methods=["POST"])
@login_required
def add_credits(org_id: int):
    _require_platform_admin()
    try:
        amount = int(request.form.get("amount", 0))
    except (ValueError, TypeError):
        abort(400)
    with session_scope() as session:
        session.execute(
            text("UPDATE public.organizations SET ai_credits_used = GREATEST(0, ai_credits_used - :a) WHERE id = :id"),
            {"a": amount, "id": org_id},
        )
        _log_admin_action(session, org_id, "credits_added", {"amount": amount})
    flash(f"Added {amount} AI credits to org {org_id}.", "success")
    return redirect(url_for("admin.org_detail", org_id=org_id))


@admin_bp.route("/org/<int:org_id>/trigger-sync", methods=["POST"])
@login_required
def trigger_sync(org_id: int):
    _require_platform_admin()
    with session_scope() as session:
        org = session.query(Organization).filter_by(id=org_id).one_or_none()
        if not org:
            abort(404)
        schema = org.schema_name
    from ..tasks.sync_tasks import sync_threads
    sync_threads.delay(org_id=org_id, schema=schema)
    flash("Sync task queued.", "info")
    return redirect(url_for("admin.org_detail", org_id=org_id))


# ── Billing overview ──────────────────────────────────────────────────────────

@admin_bp.route("/billing")
@login_required
def billing_overview():
    _require_platform_admin()
    with session_scope() as session:
        plan_counts = (
            session.query(Organization.plan, func.count(Organization.id))
            .group_by(Organization.plan)
            .all()
        )
        data = {
            "plan_counts": {plan: count for plan, count in plan_counts},
        }
    return render_template("admin/billing.html", **data)


# ── AI usage dashboard ────────────────────────────────────────────────────────

@admin_bp.route("/ai-usage")
@login_required
def ai_usage():
    _require_platform_admin()
    with session_scope() as session:
        orgs = session.query(Organization).order_by(Organization.ai_credits_used.desc()).all()
        rows = [
            {
                "id": o.id,
                "name": o.name,
                "plan": o.plan,
                "ai_credits_used": o.ai_credits_used,
                "has_byok": bool(o.openai_api_key_encrypted),
            }
            for o in orgs
        ]
    return render_template("admin/ai_usage.html", orgs=rows)


# ── Rate limit / security ─────────────────────────────────────────────────────

@admin_bp.route("/security")
@login_required
def security():
    _require_platform_admin()
    blocked_ips = _get_blocked_ips()
    return render_template("admin/security.html", blocked_ips=blocked_ips)


@admin_bp.route("/security/unblock", methods=["POST"])
@login_required
def unblock_ip():
    _require_platform_admin()
    ip_hash = request.form.get("ip_hash", "")
    if ip_hash:
        _delete_redis_key(f"addons_key_blocked:{ip_hash}")
        _delete_redis_key(f"addons_key_fail:{ip_hash}")
        _delete_redis_key(f"addons_key_lock:{ip_hash}")
        flash(f"IP {ip_hash[:12]}... unblocked.", "success")
    return redirect(url_for("admin.security"))


# ── System health ─────────────────────────────────────────────────────────────

@admin_bp.route("/health")
@login_required
def health():
    _require_platform_admin()
    from ..models import SyncHistory
    # Cross-schema: collect recent sync failures across all tenants via raw SQL
    with engine.connect() as conn:
        schemas = conn.execute(
            text("SELECT schema_name FROM public.organizations WHERE onboarding_complete = true")
        ).fetchall()

    recent_errors = []
    for (schema,) in schemas:
        try:
            with session_scope(schema=schema) as session:
                errors = (
                    session.query(SyncHistory)
                    .filter(SyncHistory.status == "failed")
                    .order_by(SyncHistory.started_at.desc())
                    .limit(3)
                    .all()
                )
                for e in errors:
                    recent_errors.append({
                        "schema": schema,
                        "error": e.error_message,
                        "started_at": e.started_at,
                    })
        except Exception:
            pass

    recent_errors.sort(key=lambda x: x["started_at"] or dt.datetime.min, reverse=True)
    return render_template("admin/health.html", recent_errors=recent_errors[:20])


# ── Helpers ───────────────────────────────────────────────────────────────────

def _log_admin_action(session, org_id: int, action: str, payload: dict):
    log = PublicAuditLog(
        org_id=org_id,
        user_id=current_user.id,
        action=action,
        payload_json=json.dumps(payload),
        ip_address=request.remote_addr,
    )
    session.add(log)


def _get_tenant_ai_usage(schema: str) -> list:
    """Get AI usage stats from a tenant schema."""
    try:
        from ..models import AIUsageLog
        with session_scope(schema=schema) as session:
            rows = (
                session.query(
                    AIUsageLog.call_type,
                    func.count(AIUsageLog.id).label("calls"),
                    func.sum(AIUsageLog.tokens_used).label("tokens"),
                )
                .group_by(AIUsageLog.call_type)
                .all()
            )
            return [{"call_type": r.call_type, "calls": r.calls, "tokens": int(r.tokens or 0)} for r in rows]
    except Exception:
        return []


def _get_blocked_ips() -> list:
    """List IPs with active Redis blocks."""
    try:
        import redis
        import os
        r = redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379/0"))
        keys = r.keys("addons_key_blocked:*")
        return [k.decode().replace("addons_key_blocked:", "") for k in keys]
    except Exception:
        return []


def _delete_redis_key(key: str):
    try:
        import redis
        import os
        r = redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379/0"))
        r.delete(key)
    except Exception as exc:
        logger.warning("Failed to delete Redis key %s: %s", key, exc)


import logging
logger = logging.getLogger(__name__)
