from flask import render_template, redirect, url_for, flash, request, jsonify

from . import bp
from .forms import DeviceForm
from ..extensions import db
from ..models import Device, ConfigChangeLog, ROLE_ADMIN
from ..decorators import role_required
from .. import crypto
from ..junos_client import JunosSession, JunosClientError


class _TransientDevice:
    """Stand-in for a Device row, used to test credentials before saving."""

    def __init__(self, hostname, port, username, auth_type, secret_plain, passphrase_plain=None):
        self.hostname = hostname
        self.port = port
        self.username = username
        self.auth_type = auth_type
        self.secret_encrypted = crypto.encrypt(secret_plain)
        self.key_passphrase_encrypted = crypto.encrypt(passphrase_plain) if passphrase_plain else None


@bp.route("/")
def list_devices():
    devices = Device.query.filter_by(is_active=True).order_by(Device.name).all()
    return render_template("devices/list.html", devices=devices)


@bp.route("/new", methods=["GET", "POST"])
@role_required(ROLE_ADMIN)
def new_device():
    form = DeviceForm()
    if form.validate_on_submit():
        secret_plain, passphrase_plain, error = _secret_from_form(form, existing=None)
        if error:
            flash(error, "danger")
            return render_template("devices/form.html", form=form, is_new=True)

        device = Device(
            name=form.name.data,
            hostname=form.hostname.data,
            port=form.port.data,
            username=form.username.data,
            auth_type=form.auth_type.data,
            secret_encrypted=crypto.encrypt(secret_plain),
            key_passphrase_encrypted=crypto.encrypt(passphrase_plain) if passphrase_plain else None,
            site=form.site.data or None,
            notes=form.notes.data or None,
        )
        db.session.add(device)
        db.session.commit()
        flash(f"Device '{device.name}' added.", "success")
        return redirect(url_for("devices.list_devices"))

    return render_template("devices/form.html", form=form, is_new=True)


@bp.route("/<int:device_id>/edit", methods=["GET", "POST"])
@role_required(ROLE_ADMIN)
def edit_device(device_id):
    device = Device.query.get_or_404(device_id)
    form = DeviceForm(obj=device)

    if request.method == "GET":
        form.password.data = ""
        form.private_key.data = ""
        form.key_passphrase.data = ""

    if form.validate_on_submit():
        secret_plain, passphrase_plain, error = _secret_from_form(form, existing=device)
        if error:
            flash(error, "danger")
            return render_template("devices/form.html", form=form, is_new=False, device=device)

        device.name = form.name.data
        device.hostname = form.hostname.data
        device.port = form.port.data
        device.username = form.username.data
        device.auth_type = form.auth_type.data
        if secret_plain is not None:
            device.secret_encrypted = crypto.encrypt(secret_plain)
        if passphrase_plain is not None:
            device.key_passphrase_encrypted = crypto.encrypt(passphrase_plain) if passphrase_plain else None
        device.site = form.site.data or None
        device.notes = form.notes.data or None
        db.session.commit()
        flash(f"Device '{device.name}' updated.", "success")
        return redirect(url_for("devices.list_devices"))

    return render_template("devices/form.html", form=form, is_new=False, device=device)


def _secret_from_form(form, existing):
    """Returns (secret_plain, passphrase_plain, error). secret_plain/passphrase_plain
    are None when the field was left blank and the existing value should be kept."""
    if form.auth_type.data == "sshkey":
        if form.private_key.data:
            return form.private_key.data, (form.key_passphrase.data or None), None
        if existing is None:
            return None, None, "A private key is required for a new SSH-key device."
        return None, (form.key_passphrase.data or None) if form.key_passphrase.data else None, None
    else:
        if form.password.data:
            return form.password.data, None, None
        if existing is None:
            return None, None, "A password is required for a new device."
        return None, None, None


@bp.route("/<int:device_id>/delete", methods=["POST"])
@role_required(ROLE_ADMIN)
def delete_device(device_id):
    device = Device.query.get_or_404(device_id)
    device.is_active = False
    db.session.commit()
    flash(f"Device '{device.name}' removed.", "info")
    return redirect(url_for("devices.list_devices"))


@bp.route("/test-connection", methods=["POST"])
@role_required(ROLE_ADMIN)
def test_connection():
    data = request.get_json(force=True)
    device_id = data.get("device_id")
    existing = Device.query.get(device_id) if device_id else None

    auth_type = data.get("auth_type", "password")
    try:
        port = int(data.get("port") or 22)
    except (TypeError, ValueError):
        return jsonify({"ok": False, "error": "Port must be a number."})

    if auth_type == "sshkey":
        secret_plain = data.get("private_key") or (crypto.decrypt(existing.secret_encrypted) if existing else None)
        passphrase_plain = data.get("key_passphrase") or None
        if not secret_plain:
            return jsonify({"ok": False, "error": "A private key is required."})
    else:
        secret_plain = data.get("password") or (crypto.decrypt(existing.secret_encrypted) if existing else None)
        passphrase_plain = None
        if not secret_plain:
            return jsonify({"ok": False, "error": "A password is required."})

    transient = _TransientDevice(
        hostname=data.get("hostname"),
        port=port,
        username=data.get("username"),
        auth_type=auth_type,
        secret_plain=secret_plain,
        passphrase_plain=passphrase_plain,
    )

    try:
        with JunosSession(transient) as session:
            facts = session.get_facts()
        return jsonify({"ok": True, "facts": facts})
    except JunosClientError as exc:
        return jsonify({"ok": False, "error": str(exc)})
    except Exception as exc:  # unexpected connectivity/library error
        return jsonify({"ok": False, "error": f"Unexpected error: {exc}"})


@bp.route("/<int:device_id>")
def device_detail(device_id):
    device = Device.query.get_or_404(device_id)
    facts = None
    interfaces = None
    config_text = None
    error = None

    try:
        with JunosSession(device) as session:
            facts = session.get_facts()
            interfaces = session.get_interface_summary()
            config_text = session.get_config()
    except JunosClientError as exc:
        error = str(exc)
    except Exception as exc:
        error = f"Unexpected error: {exc}"

    pending_log = (
        ConfigChangeLog.query.filter_by(device_id=device.id, pending_confirmation=True)
        .order_by(ConfigChangeLog.timestamp.desc())
        .first()
    )

    return render_template(
        "devices/detail.html",
        device=device,
        facts=facts,
        interfaces=interfaces,
        config_text=config_text,
        error=error,
        pending_log=pending_log,
    )
