from datetime import datetime, timezone

from flask import render_template, redirect, url_for, flash, request
from flask_login import current_user

from . import bp
from .forms import PolicerForm
from ..extensions import db
from ..models import Device, ConfigChangeLog, ROLE_ADMIN, ROLE_OPERATOR
from ..decorators import role_required
from ..junos_client import (
    JunosSession,
    JunosClientError,
    JunosAuthError,
    JunosConnectError,
    JunosLockedError,
    JunosConfigError,
    JunosDiffMismatchError,
    JunosCommitCheckError,
    JunosCommitError,
)

EDITOR_ROLES = (ROLE_ADMIN, ROLE_OPERATOR)


def _friendly_error(exc: JunosClientError) -> str:
    if isinstance(exc, JunosAuthError):
        return f"Authentication failed: {exc}"
    if isinstance(exc, JunosConnectError):
        return f"Could not connect to device: {exc}"
    if isinstance(exc, JunosLockedError):
        return f"Configuration is locked: {exc}"
    if isinstance(exc, JunosConfigError):
        return f"Invalid configuration statements: {exc}"
    if isinstance(exc, JunosDiffMismatchError):
        return str(exc)
    if isinstance(exc, JunosCommitCheckError):
        return f"Commit check failed, nothing was applied: {exc}"
    if isinstance(exc, JunosCommitError):
        return f"Commit failed: {exc}"
    return str(exc)


def _build_policer_config(form: PolicerForm) -> str:
    name = form.policer_name.data
    bandwidth = f"{form.bandwidth_value.data}{form.bandwidth_unit.data}"
    burst = f"{form.burst_value.data}{form.burst_unit.data}"
    then_clause = "discard" if form.then_action.data == "discard" else "loss-priority high"

    lines = [
        f"set firewall policer {name} if-exceeding bandwidth-limit {bandwidth} burst-size-limit {burst}",
        f"set firewall policer {name} then {then_clause}",
        f"set interfaces {form.interface.data} unit {form.unit.data} family inet "
        f"policer {form.direction.data} {name}",
    ]
    return "\n".join(lines)


@bp.route("/policer", methods=["GET", "POST"])
@role_required(*EDITOR_ROLES)
def policer_form(device_id):
    device = Device.query.get_or_404(device_id)
    form = PolicerForm()

    interfaces = None
    fetch_error = None
    try:
        with JunosSession(device) as session:
            interfaces = session.get_interface_summary()
    except JunosClientError as exc:
        fetch_error = _friendly_error(exc)
    except Exception as exc:
        fetch_error = f"Unexpected error: {exc}"

    if form.validate_on_submit():
        config_lines = _build_policer_config(form)

        try:
            with JunosSession(device) as session:
                diff = session.preview_diff(config_lines)
        except JunosClientError as exc:
            flash(_friendly_error(exc), "danger")
            return render_template(
                "config_mgmt/policer_form.html", device=device, form=form, interfaces=interfaces, fetch_error=fetch_error
            )
        except Exception as exc:
            flash(f"Unexpected error: {exc}", "danger")
            return render_template(
                "config_mgmt/policer_form.html", device=device, form=form, interfaces=interfaces, fetch_error=fetch_error
            )

        if not diff.strip():
            flash("Those settings produce no change to the configuration.", "warning")
            return render_template(
                "config_mgmt/policer_form.html", device=device, form=form, interfaces=interfaces, fetch_error=fetch_error
            )

        return render_template(
            "config_mgmt/diff.html", device=device, config_lines=config_lines, diff=diff
        )

    return render_template(
        "config_mgmt/policer_form.html", device=device, form=form, interfaces=interfaces, fetch_error=fetch_error
    )


@bp.route("/commit", methods=["POST"])
@role_required(*EDITOR_ROLES)
def commit_config(device_id):
    device = Device.query.get_or_404(device_id)
    config_lines = request.form.get("config_lines", "")
    expected_diff = request.form.get("expected_diff", "")
    comment = request.form.get("comment", "").strip()

    confirm_minutes = None
    raw_confirm = request.form.get("confirm_minutes", "").strip()
    if raw_confirm:
        try:
            confirm_minutes = int(raw_confirm)
            if confirm_minutes <= 0:
                confirm_minutes = None
        except ValueError:
            flash("Confirm timeout must be a whole number of minutes.", "danger")
            return render_template("config_mgmt/commit_failed.html", device=device, config_lines=config_lines)

    try:
        with JunosSession(device) as session:
            diff = session.commit(
                config_lines,
                comment or "committed via juniper_manager",
                expected_diff=expected_diff,
                confirm_minutes=confirm_minutes,
            )
    except JunosClientError as exc:
        log = ConfigChangeLog(
            device_id=device.id,
            user_id=current_user.id,
            action="commit_check_failed" if isinstance(exc, JunosCommitCheckError) else "commit",
            diff_text=expected_diff,
            comment=comment,
            result="failure",
            error_message=_friendly_error(exc),
        )
        db.session.add(log)
        db.session.commit()
        flash(_friendly_error(exc), "danger")
        return render_template("config_mgmt/commit_failed.html", device=device, config_lines=config_lines)
    except Exception as exc:
        flash(f"Unexpected error: {exc}", "danger")
        return render_template("config_mgmt/commit_failed.html", device=device, config_lines=config_lines)

    log = ConfigChangeLog(
        device_id=device.id,
        user_id=current_user.id,
        action="commit",
        diff_text=diff,
        comment=comment,
        result="success",
        confirm_minutes=confirm_minutes,
        pending_confirmation=bool(confirm_minutes),
    )
    db.session.add(log)
    db.session.commit()

    if confirm_minutes:
        flash(
            f"Committed with a {confirm_minutes}-minute safety timer. "
            "It will auto-revert unless you confirm it.",
            "warning",
        )
    else:
        flash("Configuration committed successfully.", "success")
    return render_template("config_mgmt/committed.html", device=device, diff=diff, log=log)


@bp.route("/confirm", methods=["POST"])
@role_required(*EDITOR_ROLES)
def confirm_commit(device_id):
    device = Device.query.get_or_404(device_id)
    log_id = request.form.get("log_id", type=int)
    pending_log = ConfigChangeLog.query.filter_by(
        id=log_id, device_id=device.id, pending_confirmation=True
    ).first()

    try:
        with JunosSession(device) as session:
            session.confirm_pending_commit("confirmed via juniper_manager")
    except JunosClientError as exc:
        flash(_friendly_error(exc), "danger")
        return redirect(url_for("devices.device_detail", device_id=device.id))
    except Exception as exc:
        flash(f"Unexpected error: {exc}", "danger")
        return redirect(url_for("devices.device_detail", device_id=device.id))

    if pending_log:
        pending_log.pending_confirmation = False
        pending_log.confirmed_at = datetime.now(timezone.utc)

    confirm_log = ConfigChangeLog(
        device_id=device.id,
        user_id=current_user.id,
        action="confirm",
        comment="Confirmed pending commit",
        result="success",
    )
    db.session.add(confirm_log)
    db.session.commit()
    flash("Commit confirmed — it will no longer auto-revert.", "success")
    return redirect(url_for("devices.device_detail", device_id=device.id))


@bp.route("/rollback", methods=["POST"])
@role_required(*EDITOR_ROLES)
def rollback_config(device_id):
    device = Device.query.get_or_404(device_id)
    rollback_id = int(request.form.get("rollback_id", 1))
    comment = request.form.get("comment", "").strip() or f"rollback to {rollback_id}"

    try:
        with JunosSession(device) as session:
            diff = session.rollback(rollback_id, comment)
    except JunosClientError as exc:
        log = ConfigChangeLog(
            device_id=device.id,
            user_id=current_user.id,
            action="rollback",
            comment=comment,
            result="failure",
            error_message=_friendly_error(exc),
            rollback_id=rollback_id,
        )
        db.session.add(log)
        db.session.commit()
        flash(_friendly_error(exc), "danger")
        return redirect(url_for("devices.device_detail", device_id=device.id))
    except Exception as exc:
        flash(f"Unexpected error: {exc}", "danger")
        return redirect(url_for("devices.device_detail", device_id=device.id))

    ConfigChangeLog.query.filter_by(device_id=device.id, pending_confirmation=True).update(
        {"pending_confirmation": False}
    )

    log = ConfigChangeLog(
        device_id=device.id,
        user_id=current_user.id,
        action="rollback",
        diff_text=diff,
        comment=comment,
        result="success",
        rollback_id=rollback_id,
    )
    db.session.add(log)
    db.session.commit()
    flash("Rollback committed successfully.", "success")
    return redirect(url_for("devices.device_detail", device_id=device.id))
