defmodule DaProductAppWeb.RiskManagementController do use DaProductAppWeb, :controller alias DaProductApp.RiskManagement # ============================================================================ # Supervisor Dashboard Endpoints # ============================================================================ @doc """ GET /api/risk/supervisor/dashboard Returns flagged transactions for supervisor review """ def supervisor_dashboard(conn, params) do supervisor_id_param = Map.get(params, "supervisor_id") || "1" supervisor_id = String.to_integer(supervisor_id_param) # Get transactions BEFORE updating last_checked_at flagged_transactions = RiskManagement.get_new_flagged_transactions(supervisor_id) # Get current last checked time (before update) last_checked = RiskManagement.get_supervisor_last_checked_time(supervisor_id) # Now update supervisor's last checked timestamp for next time RiskManagement.update_supervisor_last_checked(supervisor_id) json(conn, %{ supervisor_id: supervisor_id, new_flagged_count: length(flagged_transactions), flagged_transactions: flagged_transactions, last_checked_at: last_checked }) end @doc """ POST /api/risk/supervisor/action Updates supervisor action on flagged transaction """ def supervisor_action(conn, params) do supervisor_id = get_current_user_id(conn) hit_id = Map.get(params, "hit_id") action = Map.get(params, "action") notes = Map.get(params, "notes") case RiskManagement.update_risk_rule_hit_action(hit_id, supervisor_id, action, notes, conn) do {:ok, updated_hit} -> json(conn, %{ success: true, hit: updated_hit, message: "Action recorded successfully" }) {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "Risk rule hit not found"}) {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> json(%{ error: "Invalid action", details: format_changeset_errors(changeset) }) end end # ============================================================================ # Rule Engine Management Endpoints # ============================================================================ @doc """ GET /api/risk/rules Lists all risk rules, optionally filtered by category """ def list_rules(conn, params) do category = Map.get(params, "category") enabled_only = Map.get(params, "enabled_only", "false") == "true" rules = RiskManagement.list_risk_rules( category: category, enabled_only: enabled_only ) json(conn, %{ rules: rules, total_count: length(rules) }) end @doc """ GET /api/risk/rules/:id Gets a single risk rule by ID """ def show_rule(conn, %{"id" => id}) do try do rule = RiskManagement.get_risk_rule!(id) json(conn, %{rule: rule}) rescue Ecto.NoResultsError -> conn |> put_status(:not_found) |> json(%{error: "Risk rule not found"}) end end @doc """ POST /api/risk/rules Creates a new risk rule """ def create_rule(conn, params) do user_id = get_current_user_id(conn) case RiskManagement.create_risk_rule(params, user_id) do {:ok, rule} -> conn |> put_status(:created) |> json(%{ success: true, rule: rule, message: "Risk rule created successfully" }) {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> json(%{ error: "Failed to create risk rule", details: format_changeset_errors(changeset) }) end end @doc """ PUT /api/risk/rules/:id Updates an existing risk rule """ def update_rule(conn, %{"id" => id} = params) do user_id = get_current_user_id(conn) rule = RiskManagement.get_risk_rule!(id) case RiskManagement.update_risk_rule(rule, params, user_id) do {:ok, updated_rule} -> json(conn, %{ success: true, rule: updated_rule, message: "Risk rule updated successfully" }) {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> json(%{ error: "Failed to update risk rule", details: format_changeset_errors(changeset) }) end rescue Ecto.NoResultsError -> conn |> put_status(:not_found) |> json(%{error: "Risk rule not found"}) end @doc """ DELETE /api/risk/rules/:id Deletes a risk rule """ def delete_rule(conn, %{"id" => id}) do user_id = get_current_user_id(conn) rule = RiskManagement.get_risk_rule!(id) case RiskManagement.delete_risk_rule(rule, user_id) do {:ok, _deleted_rule} -> json(conn, %{ success: true, message: "Risk rule deleted successfully" }) {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> json(%{ error: "Failed to delete risk rule", details: format_changeset_errors(changeset) }) end rescue Ecto.NoResultsError -> conn |> put_status(:not_found) |> json(%{error: "Risk rule not found"}) end # ============================================================================ # Statistics and Reporting Endpoints # ============================================================================ @doc """ GET /api/risk/statistics Returns rule hit statistics and performance metrics """ def statistics(conn, params) do days_back = Map.get(params, "days_back", "30") |> String.to_integer() stats = RiskManagement.get_rule_statistics(days_back: days_back) json(conn, %{ period_days: days_back, rule_statistics: stats, total_hits: Enum.reduce(stats, 0, & &1.hit_count + &2), generated_at: DateTime.utc_now() }) end @doc """ POST /api/risk/evaluate Manually evaluate a transaction against risk rules (for testing) """ def evaluate_transaction(conn, params) do transaction_id = Map.get(params, "transaction_id") transaction_type = Map.get(params, "transaction_type", "QR") # This would typically be called automatically during transaction processing # but can be useful for testing and manual evaluation json(conn, %{ message: "Transaction evaluation endpoint - implementation needed", transaction_id: transaction_id, transaction_type: transaction_type }) end @doc """ GET /api/risk/audit-logs Returns audit logs for risk management activities """ def audit_logs(conn, params) do days_back = Map.get(params, "days_back", "30") |> String.to_integer() user_id = Map.get(params, "user_id") event_type = Map.get(params, "event_type") limit = Map.get(params, "limit", "100") |> String.to_integer() logs = RiskManagement.get_audit_logs( days_back: days_back, user_id: user_id, event_type: event_type, limit: limit ) summary = RiskManagement.get_audit_summary(days_back: days_back) json(conn, %{ audit_logs: logs, summary: summary, filters: %{ days_back: days_back, user_id: user_id, event_type: event_type, limit: limit } }) end # ============================================================================ # Helper Functions # ============================================================================ defp get_current_user_id(conn) do # Extract user ID from session/authentication # This should be replaced with actual authentication logic case conn.assigns[:current_user] do %{id: user_id} -> user_id _ -> 1 # Default supervisor ID for testing end end defp format_changeset_errors(changeset) do Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> Enum.reduce(opts, message, fn {key, value}, acc -> String.replace(acc, "%{#{key}}", to_string(value)) end) end) end end