# 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

"""
Workflow Templates Skill

Pre-built multi-step workflows for recurring nonprofit operations.
Each workflow is a sequence of tool calls with parameter mapping.

Data stored in ~/.familiar/data/workflows.json
Dependencies: None (orchestration over existing skills)
"""

import json
import logging
from datetime import datetime, timedelta
from pathlib import Path


def _get_data_dir():
    """Get data directory (tenant-scoped in Reflection, default in standalone)."""
    try:
        from familiar.core import paths

        return paths.DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


try:
    from familiar.core.utils import atomic_write_json, generate_id, safe_load_json
except ImportError:
    atomic_write_json = None
    safe_load_json = None
    generate_id = None

logger = logging.getLogger(__name__)


def _get_data_file():
    """Get data file path (re-evaluates for tenant scoping)."""
    return _get_data_dir() / "workflows.json"


DATA_FILE = _get_data_file()  # Default for backward compat

_DEFAULT_DATA = {"runs": [], "custom_workflows": [], "version": 1}


def _load_data():
    if safe_load_json:
        return safe_load_json(
            _get_data_file(), default=lambda: json.loads(json.dumps(_DEFAULT_DATA))
        )
    if _get_data_file().exists():
        try:
            with open(_get_data_file(), encoding="utf-8") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
    return json.loads(json.dumps(_DEFAULT_DATA))


def _save_data(data):
    if atomic_write_json:
        atomic_write_json(_get_data_file(), data)
    else:
        _get_data_file().parent.mkdir(parents=True, exist_ok=True)
        with open(_get_data_file(), "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)


def _gen_id():
    if generate_id:
        return generate_id()
    import secrets

    return secrets.token_hex(4)


# === Bundled Workflow Definitions ===

BUNDLED_WORKFLOWS = {
    "new_donor_onboarding": {
        "name": "New Donor Onboarding",
        "description": "Log donor → add contact → generate receipt → draft thank-you letter → create follow-up task",
        "required_params": ["donor_name", "amount"],
        "optional_params": ["email", "phone", "organization"],
        "steps": [
            {
                "tool": "log_donor",
                "params": {
                    "name": "{donor_name}",
                    "type": "gift",
                    "amount": "{amount}",
                    "email": "{email}",
                    "phone": "{phone}",
                },
            },
            {
                "tool": "add_contact",
                "params": {
                    "name": "{donor_name}",
                    "email": "{email}",
                    "phone": "{phone}",
                    "organization": "{organization}",
                    "tags": ["donor"],
                },
            },
            {
                "tool": "log_income",
                "params": {
                    "amount": "{amount}",
                    "category": "individual_donations",
                    "source": "{donor_name}",
                },
            },
            {
                "tool": "create_receipt",
                "params": {"donor_name": "{donor_name}", "amount": "{amount}"},
            },
            {
                "tool": "create_letter",
                "params": {
                    "recipient": "{donor_name}",
                    "body": "Dear {donor_name},\n\nThank you for your generous gift of ${amount}. Your support makes a real difference in our mission.\n\nWe are grateful for your partnership and look forward to keeping you informed about the impact of your generosity.\n\nWith sincere thanks,",
                    "subject": "Thank You for Your Gift",
                },
            },
            {
                "tool": "add_task",
                "params": {
                    "title": "Follow-up call: {donor_name} (new donor)",
                    "priority": "high",
                    "category": "donor_stewardship",
                    "due_date": "+7d",
                },
            },
        ],
    },
    "grant_application": {
        "name": "Grant Application Pipeline",
        "description": "Add grant → create tasks for each milestone → set deadline alerts",
        "required_params": ["grant_name", "funder", "deadline"],
        "optional_params": ["amount"],
        "steps": [
            {
                "tool": "add_grant",
                "params": {
                    "name": "{grant_name}",
                    "funder": "{funder}",
                    "deadline": "{deadline}",
                    "amount": "{amount}",
                    "status": "prospect",
                },
            },
            {
                "tool": "add_task",
                "params": {
                    "title": "Draft LOI: {grant_name}",
                    "priority": "high",
                    "category": "grants",
                    "due_date": "-30d:{deadline}",
                },
            },
            {
                "tool": "add_task",
                "params": {
                    "title": "Write narrative: {grant_name}",
                    "priority": "high",
                    "category": "grants",
                    "due_date": "-14d:{deadline}",
                },
            },
            {
                "tool": "add_task",
                "params": {
                    "title": "Prepare budget: {grant_name}",
                    "priority": "normal",
                    "category": "grants",
                    "due_date": "-14d:{deadline}",
                },
            },
            {
                "tool": "add_task",
                "params": {
                    "title": "Internal review: {grant_name}",
                    "priority": "normal",
                    "category": "grants",
                    "due_date": "-7d:{deadline}",
                },
            },
            {
                "tool": "add_task",
                "params": {
                    "title": "Submit application: {grant_name}",
                    "priority": "urgent",
                    "category": "grants",
                    "due_date": "{deadline}",
                },
            },
        ],
    },
    "board_meeting_prep": {
        "name": "Board Meeting Prep",
        "description": "Generate board packet → create agenda → schedule meeting → notify attendees",
        "required_params": ["meeting_date", "meeting_time"],
        "optional_params": ["attendees", "topics", "location"],
        "steps": [
            {
                "tool": "board_packet",
                "params": {"meeting_date": "{meeting_date}", "format": "xlsx"},
            },
            {
                "tool": "create_agenda",
                "params": {
                    "title": "Board Meeting - {meeting_date}",
                    "topics": "{topics}",
                    "include_prior_actions": True,
                },
            },
            {
                "tool": "schedule_meeting",
                "params": {
                    "title": "Board Meeting",
                    "start": "{meeting_date}T{meeting_time}",
                    "duration_minutes": 90,
                    "attendees": "{attendees}",
                    "location": "{location}",
                },
            },
        ],
    },
    "year_end_acknowledgments": {
        "name": "Year-End Acknowledgments",
        "description": "Find all donors who gave this year → generate receipt + thank-you for each",
        "required_params": [],
        "optional_params": ["year"],
        "steps": [
            {"tool": "search_donors", "params": {"query": ""}},
            {"tool": "donor_report", "params": {"format": "xlsx", "period": "{year}"}},
            # Note: actual per-donor iteration is described to the LLM; the workflow
            # generates the report and donor list for the agent to act on.
        ],
    },
    "monthly_close": {
        "name": "Monthly Close",
        "description": "Financial summary → donor report → grant report → export to XLSX",
        "required_params": [],
        "optional_params": ["month"],
        "steps": [
            {"tool": "financial_summary", "params": {}},
            {"tool": "donor_report", "params": {"format": "xlsx"}},
            {"tool": "grant_report", "params": {"format": "xlsx"}},
            {"tool": "export_to_xlsx", "params": {"dataset": "transactions"}},
        ],
    },
}


def _resolve_param(value, params):
    """Resolve {param} placeholders in a value."""
    if isinstance(value, str):
        for key, val in params.items():
            value = value.replace(f"{{{key}}}", str(val) if val else "")
        # Handle relative dates like "+7d" or "-30d:{deadline}"
        if value.startswith("+") and value.endswith("d"):
            try:
                days = int(value[1:-1])
                return (datetime.now() + timedelta(days=days)).strftime("%Y-%m-%d")
            except ValueError as e:
                logger.debug("Value conversion error suppressed: %s", e)
        if "d:" in value and value.startswith("-"):
            try:
                parts = value.split(":")
                days = int(parts[0].replace("d", "")[1:])
                base_date = parts[1]
                base = datetime.strptime(base_date, "%Y-%m-%d").date()
                return (base - timedelta(days=days)).strftime("%Y-%m-%d")
            except (ValueError, IndexError) as e:
                logger.debug("Value conversion error suppressed: %s", e)
        return value
    elif isinstance(value, list):
        return [_resolve_param(v, params) for v in value]
    elif isinstance(value, dict):
        return {k: _resolve_param(v, params) for k, v in value.items()}
    return value


# === Tool Handlers ===


def run_workflow(data):
    """Execute a named workflow with provided parameters."""
    workflow_name = data.get("workflow", "").strip().lower().replace(" ", "_")
    params = data.get("params", {})

    # Check bundled workflows
    wf = BUNDLED_WORKFLOWS.get(workflow_name)
    if not wf:
        # Check custom workflows
        db = _load_data()
        for cw in db.get("custom_workflows", []):
            if cw["name"].lower().replace(" ", "_") == workflow_name:
                wf = cw
                break

    if not wf:
        available = list(BUNDLED_WORKFLOWS.keys())
        return f"Workflow '{workflow_name}' not found. Available: {', '.join(available)}"

    # Check required params
    missing = [p for p in wf.get("required_params", []) if p not in params or not params[p]]
    if missing:
        return f"Missing required parameters: {', '.join(missing)}\nWorkflow: {wf['name']}\nRequired: {', '.join(wf.get('required_params', []))}"

    # Set defaults
    if "year" not in params:
        params["year"] = str(datetime.now().year)

    # Create run record
    run_id = _gen_id()
    run = {
        "id": run_id,
        "workflow": workflow_name,
        "params": params,
        "steps": [],
        "status": "running",
        "started_at": datetime.now().isoformat(),
    }

    db = _load_data()

    # Execute steps
    lines = [f"🔄 Running workflow: {wf['name']} [{run_id}]\n"]

    for i, step in enumerate(wf.get("steps", []), 1):
        tool_name = step["tool"]
        tool_params = _resolve_param(step.get("params", {}), params)

        # Clean empty string params
        tool_params = {k: v for k, v in tool_params.items() if v != "" and v is not None}

        step_record = {
            "step": i,
            "tool": tool_name,
            "params": tool_params,
            "status": "pending",
        }

        # Try to execute via the tool registry
        result = _execute_tool(tool_name, tool_params)
        step_record["result"] = result[:200] if result else "no output"
        step_record["status"] = "done" if not result.startswith("❌") else "failed"

        run["steps"].append(step_record)
        emoji = "✅" if step_record["status"] == "done" else "❌"
        lines.append(f"  {emoji} Step {i}: {tool_name}")
        lines.append(f"     {result[:120]}")

    run["status"] = "completed" if all(s["status"] == "done" for s in run["steps"]) else "partial"
    run["completed_at"] = datetime.now().isoformat()

    db["runs"].append(run)
    if len(db["runs"]) > 100:
        db["runs"] = db["runs"][-100:]
    _save_data(db)

    completed = sum(1 for s in run["steps"] if s["status"] == "done")
    total = len(run["steps"])
    lines.append(
        f"\n{'✅' if run['status'] == 'completed' else '⚠️'} Workflow {run['status']}: {completed}/{total} steps"
    )

    return "\n".join(lines)


def _execute_tool(tool_name, params):
    """Execute a tool by name. Tries skill modules directly."""
    # Map tool names to skill modules
    skill_modules = {
        "log_donor": ("nonprofit", "log_donor"),
        "add_contact": ("contacts", "add_contact"),
        "search_donors": ("nonprofit", "search_donors"),
        "log_income": ("bookkeeping", "log_income"),
        "create_receipt": ("documents", "create_receipt"),
        "create_letter": ("documents", "create_letter"),
        "add_task": ("tasks", "add_task"),
        "add_grant": ("nonprofit", "add_grant"),
        "board_packet": ("reports", "board_packet"),
        "create_agenda": ("meetings", "create_agenda"),
        "schedule_meeting": ("meetings", "schedule_meeting"),
        "financial_summary": ("bookkeeping", "financial_summary"),
        "donor_report": ("reports", "donor_report"),
        "grant_report": ("reports", "grant_report"),
        "export_to_xlsx": ("reports", "export_to_xlsx"),
        "send_notification": ("notifications", "send_notification"),
    }

    if tool_name not in skill_modules:
        return f"⚠️ Tool '{tool_name}' not mapped in workflow engine"

    skill_name, func_name = skill_modules[tool_name]
    skill_dir = Path(__file__).parent.parent / skill_name

    try:
        import importlib.util
        import sys

        spec = importlib.util.spec_from_file_location(f"skill_{skill_name}", skill_dir / "skill.py")
        module = importlib.util.module_from_spec(spec)
        # Don't re-register if already loaded
        mod_key = f"wf_skill_{skill_name}"
        if mod_key not in sys.modules:
            sys.modules[mod_key] = module
            spec.loader.exec_module(module)
        else:
            module = sys.modules[mod_key]

        handler = getattr(module, func_name, None)
        if handler and callable(handler):
            return str(handler(params))
        return f"⚠️ Function '{func_name}' not found in {skill_name}"
    except Exception as e:
        logger.warning(f"Workflow tool execution error ({tool_name}): {e}")
        return f"⚠️ Error executing {tool_name}: {e}"


def list_workflows(data):
    """Show available workflow templates."""
    db = _load_data()
    lines = ["📋 Available Workflows:\n"]

    lines.append("  BUNDLED:")
    for key, wf in BUNDLED_WORKFLOWS.items():
        req = ", ".join(wf.get("required_params", [])) or "none"
        lines.append(f"    • {key}: {wf['description']}")
        lines.append(f"      Required: {req} | Steps: {len(wf.get('steps', []))}")

    custom = db.get("custom_workflows", [])
    if custom:
        lines.append("\n  CUSTOM:")
        for wf in custom:
            lines.append(f"    • {wf['name']}: {wf.get('description', '')}")
            lines.append(f"      Steps: {len(wf.get('steps', []))}")

    return "\n".join(lines)


def create_workflow(data):
    """Define a new workflow from a sequence of steps."""
    name = data.get("name", "").strip()
    description = data.get("description", "").strip()
    steps = data.get("steps", [])

    if not name:
        return "Please provide a workflow name."
    if not steps:
        return "Please provide workflow steps. Each step: {tool: 'tool_name', params: {...}}"

    db = _load_data()

    # Validate steps
    for i, step in enumerate(steps):
        if not step.get("tool"):
            return f"Step {i + 1} missing 'tool' field."

    workflow = {
        "name": name,
        "description": description,
        "required_params": data.get("required_params", []),
        "optional_params": data.get("optional_params", []),
        "steps": steps,
        "created_at": datetime.now().isoformat(),
    }

    # Replace existing with same name
    db["custom_workflows"] = [
        w for w in db.get("custom_workflows", []) if w["name"].lower() != name.lower()
    ]
    db["custom_workflows"].append(workflow)
    _save_data(db)

    return f"✅ Workflow created: {name} ({len(steps)} steps)"


def workflow_status(data):
    """Check progress of recent workflow runs."""
    db = _load_data()
    run_id = data.get("run_id", "").strip()
    limit = min(data.get("limit", 10), 50)

    if run_id:
        for r in db.get("runs", []):
            if r["id"] == run_id:
                lines = [f"🔄 Workflow Run: {r['workflow']} [{r['id']}]"]
                lines.append(f"   Status: {r['status']}")
                lines.append(f"   Started: {r.get('started_at', '')[:16]}")
                if r.get("completed_at"):
                    lines.append(f"   Completed: {r['completed_at'][:16]}")
                lines.append("\n   Steps:")
                for s in r.get("steps", []):
                    emoji = "✅" if s["status"] == "done" else "❌"
                    lines.append(
                        f"     {emoji} {s['step']}. {s['tool']}: {s.get('result', '')[:80]}"
                    )
                return "\n".join(lines)
        return f"Run {run_id} not found."

    runs = db.get("runs", [])[-limit:]
    runs.reverse()

    if not runs:
        return "No workflow runs found."

    lines = [f"🔄 Recent Workflow Runs ({len(runs)}):\n"]
    for r in runs:
        emoji = "✅" if r["status"] == "completed" else "⚠️" if r["status"] == "partial" else "🔄"
        dt = r.get("started_at", "")[:16].replace("T", " ")
        steps_done = sum(1 for s in r.get("steps", []) if s.get("status") == "done")
        total = len(r.get("steps", []))
        lines.append(f"  {emoji} [{r['id']}] {r['workflow']} ({steps_done}/{total} steps) — {dt}")

    return "\n".join(lines)


# === Tool Definitions ===

TOOLS = [
    {
        "name": "run_workflow",
        "description": (
            "Execute a named workflow with provided parameters, running each step sequentially. "
            "Supports both bundled workflows (new_donor_onboarding, grant_application, board_meeting_prep, etc.) and custom user-defined workflows. "
            "Each step resolves parameter placeholders and executes the corresponding tool. "
            "Returns a detailed report of step-by-step results with success/failure status for each."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "workflow": {
                    "type": "string",
                    "description": "Workflow name: new_donor_onboarding, grant_application, board_meeting_prep, year_end_acknowledgments, monthly_close, or custom name",
                },
                "params": {
                    "type": "object",
                    "description": "Parameters for the workflow (e.g. donor_name, amount, deadline)",
                },
            },
            "required": ["workflow"],
        },
        "handler": run_workflow,
        "category": "workflows",
    },
    {
        "name": "list_workflows",
        "description": (
            "Show all available workflow templates including both bundled and custom user-defined workflows. "
            "Displays each workflow's description, required parameters, and number of steps. "
            "Bundled workflows cover common nonprofit operations like donor onboarding and grant applications. "
            "Custom workflows created with create_workflow are listed separately."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": list_workflows,
        "category": "workflows",
    },
    {
        "name": "create_workflow",
        "description": (
            "Define a new custom workflow from a sequence of tool steps with parameter placeholders. "
            "Each step specifies a tool name and parameters that can reference workflow-level parameters using {param} syntax. "
            "Replaces any existing workflow with the same name. "
            "The workflow is saved to disk and can be executed with run_workflow."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Workflow name"},
                "description": {
                    "type": "string",
                    "description": "Human-readable description of what the workflow does",
                },
                "steps": {
                    "type": "array",
                    "description": "Sequence of {tool, params} steps",
                    "items": {"type": "object"},
                },
                "required_params": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Parameters that must be provided when running",
                },
                "optional_params": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Parameters that may be provided but are not required",
                },
            },
            "required": ["name", "steps"],
        },
        "input_examples": [
            {
                "name": "morning-briefing",
                "description": "Daily morning briefing workflow",
                "steps": [
                    {"tool": "check_inbox", "params": {"unread_only": True}},
                    {"tool": "calendar_today", "params": {}},
                    {"tool": "task_briefing", "params": {}},
                ],
            },
        ],
        "handler": create_workflow,
        "category": "workflows",
    },
    {
        "name": "workflow_status",
        "description": (
            "Check the progress and results of recent workflow runs or look up a specific run by ID. "
            "Shows step-by-step completion status, timestamps, and result summaries for each run. "
            "Without a run_id, returns the most recent runs in reverse chronological order. "
            "Useful for monitoring long-running workflows and diagnosing step failures."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "run_id": {
                    "type": "string",
                    "description": "Specific run ID (omit for recent runs)",
                },
                "limit": {
                    "type": "integer",
                    "default": 10,
                    "description": "Maximum number of recent runs to return (capped at 50)",
                },
            },
        },
        "handler": workflow_status,
        "category": "workflows",
    },
]
