"""
Workflow Engine
===============
Stateful multi-step workflow executor for the BizNode dashboard/bot.

The engine is hosted in the dashboard (ui/server.py) and called via REST
endpoints. The Telegram bot interacts through:
  POST /api/workflow-state/{user_id}/start
  POST /api/workflow-state/{user_id}/advance

Supported step types:
  collect_input   — prompt user, save answer under field_name in collected_data
  send_otp        — generate + email OTP to collected_data['email']
  validate_otp    — verify OTP input (max 3 attempts)
  send_email      — send collected_data['details'] to collected_data['email']
  show_message    — display step_prompt, auto-advance
"""

import json
import logging
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

from services.otp_service import create_and_send_otp, validate_user_otp

logger = logging.getLogger("biznode.workflow")


# ── Internal helpers ──────────────────────────────────────────────────────────

def _get_step(steps: list, step_order: int) -> dict:
    """Return the step with the given order number, or None."""
    return next((s for s in steps if s["step_order"] == step_order), None)


def _load_data(state: dict) -> dict:
    """Safely parse collected_data JSON from a state row."""
    try:
        return json.loads(state.get("collected_data") or "{}")
    except Exception:
        return {}


def _next_step_response(state: dict, steps: list, db) -> str:
    """
    Advance current_step by 1 and return the next step's prompt.
    If no next step exists, complete the workflow.
    """
    next_order = state["current_step"] + 1
    next_step  = _get_step(steps, next_order)

    if not next_step:
        db.update_workflow_state(state["user_id"], status="completed")
        return "Workflow completed successfully."

    db.update_workflow_state(state["user_id"], current_step=next_order)

    # send_otp and show_message auto-execute — recurse via advance_workflow
    if next_step["step_type"] in ("send_otp", "show_message"):
        refreshed = db.get_active_workflow_state(state["user_id"])
        if refreshed:
            return _execute_step(next_step, refreshed, steps, db, user_input=None)

    return next_step.get("step_prompt") or f"Step {next_order}"


def _send_details_email(to_email: str, details: str) -> bool:
    """Send collected workflow details to the verified email address."""
    smtp_host = os.getenv("SMTP_HOST", "")
    smtp_port = int(os.getenv("SMTP_PORT", "587"))
    smtp_user = os.getenv("SMTP_USER", "") or os.getenv("OWNER_EMAIL", "")
    smtp_pass = os.getenv("SMTP_PASSWORD", "")

    if not smtp_host or not smtp_user:
        logger.error("SMTP not configured — cannot send details email")
        return False

    msg = MIMEMultipart("alternative")
    msg["Subject"] = "BizNode — Your Requested Information"
    msg["From"]    = smtp_user
    msg["To"]      = to_email
    msg.attach(MIMEText(f"Here are the details you requested:\n\n{details}", "plain"))

    try:
        with smtplib.SMTP(smtp_host, smtp_port, timeout=10) as server:
            server.starttls()
            server.login(smtp_user, smtp_pass)
            server.sendmail(smtp_user, to_email, msg.as_string())
        return True
    except Exception as exc:
        logger.error("Failed to send details email: %s", exc)
        return False


def _execute_step(step: dict, state: dict, steps: list, db, user_input: str) -> str:
    """Execute a single workflow step and return the bot response."""
    step_type = step["step_type"]
    data      = _load_data(state)

    if step_type == "collect_input":
        if user_input is None:
            return step.get("step_prompt") or "Please provide your input:"
        field = step.get("field_name") or f"field_{step['step_order']}"
        data[field] = user_input
        db.update_workflow_state(
            state["user_id"],
            collected_data=json.dumps(data),
        )
        return _next_step_response(state, steps, db)

    elif step_type == "send_otp":
        email = data.get("email", "").strip()
        if not email:
            db.update_workflow_state(state["user_id"], status="failed")
            return "Workflow failed: no email address collected before OTP step."
        ok, msg = create_and_send_otp(state["user_id"], email, db)
        if not ok:
            db.update_workflow_state(state["user_id"], status="failed")
            return f"Could not send OTP: {msg}"
        return _next_step_response(state, steps, db)

    elif step_type == "validate_otp":
        if user_input is None:
            return step.get("step_prompt") or "Please enter the OTP sent to your email:"
        ok, msg = validate_user_otp(state["user_id"], user_input, db)
        if ok:
            return _next_step_response(state, steps, db)
        # Workflow cancelled by validate_user_otp on max attempts
        if "cancelled" in msg.lower() or "no attempts" in msg.lower():
            db.update_workflow_state(state["user_id"], status="failed")
        return msg

    elif step_type == "send_email":
        email   = data.get("email", "").strip()
        details = data.get("details", "").strip()
        if not email:
            db.update_workflow_state(state["user_id"], status="failed")
            return "Workflow failed: missing verified email address."
        ok = _send_details_email(email, details or "(no details provided)")
        db.update_workflow_state(state["user_id"], status="completed")
        if ok:
            return f"Details sent to {email}. Workflow complete."
        return "Workflow complete, but the email could not be delivered (check SMTP settings)."

    elif step_type == "show_message":
        msg = step.get("step_prompt") or "Noted."
        return f"{msg}\n\n{_next_step_response(state, steps, db)}"

    else:
        logger.warning("Unknown step_type: %s", step_type)
        return _next_step_response(state, steps, db)


# ── Public API ────────────────────────────────────────────────────────────────

def start_workflow(user_id: str, trigger_command: str, db) -> str:
    """
    Find an active workflow matching trigger_command (case-insensitive),
    create a fresh state record, and return the first step prompt.

    Returns None if no matching workflow is found.
    """
    wf = db.get_workflow_by_command(trigger_command.strip())
    if not wf:
        return None

    steps = db.get_workflow_steps(wf["workflow_code"])
    if not steps:
        return "This workflow has no steps configured yet."

    db.start_workflow_state(str(user_id), wf["workflow_code"])
    state    = db.get_active_workflow_state(str(user_id))
    first    = _get_step(steps, 1)
    if not first:
        return "Workflow configuration error: step 1 not found."

    # Auto-execute non-input steps at position 1
    if first["step_type"] in ("send_otp", "show_message"):
        return _execute_step(first, state, steps, db, user_input=None)

    return first.get("step_prompt") or "Workflow started."


def advance_workflow(user_id: str, user_input: str, db) -> str:
    """
    Advance the active workflow for a user by one step.

    Returns the next prompt/message, or None if the user is not in a workflow.
    """
    state = db.get_active_workflow_state(str(user_id))
    if not state:
        return None

    steps   = db.get_workflow_steps(state["workflow_code"])
    current = _get_step(steps, state["current_step"])

    if not current:
        db.update_workflow_state(str(user_id), status="completed")
        return "Workflow completed."

    return _execute_step(current, state, steps, db, user_input=user_input)
