← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-digital-samples/digital-samples-agent.ts.backup-auth-1762615744

1138 lines

/**
 * DW-Agents: Digital Samples Agent
 *
 * Monitors Shopify orders for DIG-series products and automates sample delivery:
 * 1. Detects DIG-* SKU orders from Shopify webhooks
 * 2. Searches Google Drive for matching sample files
 * 3. Resizes images to 24" wide at 150 DPI
 * 4. Posts to Slack #new-products channel with approval workflow
 * 5. Sends files to customers via email
 *
 * Port: 9879
 */

import Anthropic from '@anthropic-ai/sdk';
import express from 'express';
import { requireAuth as ssoAuth, handleLogin, setSSOToken, SSO_TOKEN_NAME, SSO_TOKEN_VALUE } from './shared-auth';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import { WebClient } from '@slack/web-api';
import sharp from 'sharp';
import fetch from 'node-fetch';
import { google } from 'googleapis';
import dotenv from 'dotenv';

// Load environment variables
dotenv.config();

const app = express();
const PORT = 9879;

// Authentication
const AUTH_USERNAME = 'admin';
const AUTH_PASSWORD = '2025';

// Shopify configuration
const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
const SHOPIFY_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN || 'shpat_REDACTED';
const API_VERSION = '2024-07';

// Slack configuration
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN || 'xoxb-3958182050256-9814366234406-HiR1ybK6HT8JViWDEpjXpmsq';
const SLACK_CHANNEL = 'new-products-on-shopify';
const slackClient = new WebClient(SLACK_BOT_TOKEN);

// Anthropic Claude
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

// Google Drive API setup
const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  'http://localhost'
);

oauth2Client.setCredentials({
  refresh_token: process.env.GOOGLE_REFRESH_TOKEN
});

const drive = google.drive({ version: 'v3', auth: oauth2Client });

// Statistics tracking
let stats = {
  totalOrders: 0,
  digOrdersFound: 0,
  filesProcessed: 0,
  slackPostsSent: 0,
  emailsSent: 0,
  lastOrder: null as {
    orderId: string;
    customerName: string;
    digItems: string[];
    timestamp: Date;
  } | null,
  pendingApprovals: [] as Array<{
    id: string;
    orderId: string;
    customerEmail: string;
    customerName: string;
    digSku: string;
    productTitle: string;
    imageUrl: string;
    resizedImageUrl: string;
    status: 'pending' | 'approved' | 'rejected';
    timestamp: Date;
  }>
};

// Activity log
const activityLog: Array<{
  timestamp: Date;
  type: 'order' | 'processing' | 'slack' | 'email' | 'error';
  message: string;
}> = [];

function addLog(message: string, type: 'order' | 'processing' | 'slack' | 'email' | 'error' = 'processing') {
  const entry = {
    timestamp: new Date(),
    type,
    message
  };
  activityLog.unshift(entry);

  // Keep only last 100 entries
  if (activityLog.length > 100) {
    activityLog.pop();
  }

  console.log(`[${type.toUpperCase()}] ${message}`);
}

app.use(express.json());
app.use(cookieParser());
app.use(express.urlencoded({ extended: true }));

app.use(
  session({
    secret: 'dw-digital-samples-agent-2025',
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: false,
      httpOnly: true,
      maxAge: 24 * 60 * 60 * 1000,
    },
  })
);

declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
  }
}

const requireAuth = (req: express.Request, res: express.Response, next: express.NextFunction) => {
  // Check session first
  if (req.session.authenticated) {
    return next();
  }

  // Check SSO cookie
  if (req.cookies && req.cookies[SSO_TOKEN_NAME] === SSO_TOKEN_VALUE) {
    req.session.authenticated = true;
    return next();
  }

  res.redirect('/login');
};

// Login page
app.get('/login', (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Login - Digital Samples Agent</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
    }
    .login-container {
      background: white;
      padding: 40px;
      border-radius: 15px;
      box-shadow: 0 15px 35px rgba(0,0,0,0.2);
      max-width: 400px;
      width: 100%;
    }
    h1 { color: #4facfe; text-align: center; margin-bottom: 30px; }
    input {
      width: 100%;
      padding: 12px;
      margin: 10px 0;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
    }
    button {
      width: 100%;
      background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
      color: white;
      border: none;
      padding: 15px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      margin-top: 10px;
    }
    .error { color: red; text-align: center; margin-bottom: 15px; }
  </style>
</head>
<body>
  <div class="login-container">
    <h1>📦 Digital Samples Agent</h1>
    ${req.query.error ? '<div class="error">Invalid credentials</div>' : ''}
    <form method="POST">
      <input type="text" name="username" placeholder="Username" required>
      <input type="password" name="password" placeholder="Password" required>
      <button type="submit">Login</button>
    </form>
  </div>
</body>
</html>
  `);
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username === AUTH_USERNAME && password === AUTH_PASSWORD) {
    req.session.authenticated = true;

    // Set SSO cookie for cross-agent authentication
    setSSOToken(res);

    res.redirect('/');
  } else {
    res.redirect('/login?error=1');
  }
});

app.get('/logout', (req, res) => {
  req.session.destroy(() => res.redirect('/login'));
});

// Main dashboard
app.get('/', requireAuth, (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Digital Samples Agent - DW-Agents</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
      padding: 20px;
      min-height: 100vh;
    }
    .header {
      background: white;
      padding: 30px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .header h1 { color: #4facfe; font-size: 2.5em; margin-bottom: 10px; }
    .header .subtitle { color: #666; font-size: 1.2em; }
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 20px;
      margin-bottom: 20px;
    }
    .stat-card {
      background: white;
      padding: 25px;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .stat-card h3 { color: #888; font-size: 0.9em; text-transform: uppercase; margin-bottom: 10px; }
    .stat-card .value { font-size: 2.5em; font-weight: bold; color: #4facfe; }
    .approvals-section {
      background: white;
      padding: 30px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .approvals-section h2 { color: #4facfe; margin-bottom: 20px; }
    .approval-card {
      background: #f8f9fa;
      padding: 20px;
      border-radius: 10px;
      margin-bottom: 15px;
      border-left: 4px solid #4facfe;
    }
    .approval-card img { max-width: 200px; border-radius: 8px; margin: 10px 0; }
    .btn {
      padding: 10px 20px;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      margin-right: 10px;
    }
    .btn-approve { background: #4CAF50; color: white; }
    .btn-reject { background: #f44336; color: white; }
    .log-section {
      background: white;
      padding: 30px;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .log-entry {
      padding: 10px;
      margin: 5px 0;
      border-radius: 6px;
      font-family: 'Courier New', monospace;
      font-size: 0.9em;
    }
    .log-entry.order { background: #e3f2fd; border-left: 4px solid #2196F3; }
    .log-entry.processing { background: #fff3e0; border-left: 4px solid #FF9800; }
    .log-entry.slack { background: #f3e5f5; border-left: 4px solid #9C27B0; }
    .log-entry.email { background: #e8f5e9; border-left: 4px solid #4CAF50; }
    .log-entry.error { background: #ffebee; border-left: 4px solid #f44336; }
    .drive-file-card {
      background: #f8f9fa;
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 10px;
      border-left: 4px solid #4facfe;
      cursor: pointer;
      transition: all 0.2s;
      display: flex;
      gap: 15px;
      align-items: center;
    }
    .drive-file-card:hover {
      background: #e9ecef;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .drive-file-card.selected {
      background: #e8f5e9;
      border-left-color: #4CAF50;
    }
    .drive-file-thumbnail {
      width: 60px;
      height: 60px;
      border-radius: 6px;
      object-fit: cover;
      background: #e0e0e0;
      flex-shrink: 0;
    }
    .drive-file-info {
      flex: 1;
    }
    .drive-file-name {
      font-weight: 600;
      color: #333;
      margin-bottom: 5px;
    }
    .drive-file-meta {
      color: #888;
      font-size: 0.85em;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>📦 Digital Samples Agent</h1>
    <div class="subtitle">Automated DIG-series order processing & delivery</div>
    <div style="text-align: right; margin-top: 10px;">
      <a href="/logout" style="color: #4facfe; text-decoration: none;">Logout</a>
    </div>
  </div>

  <div class="stats-grid">
    <div class="stat-card">
      <h3>Total Orders</h3>
      <div class="value" id="totalOrders">0</div>
    </div>
    <div class="stat-card">
      <h3>DIG Orders</h3>
      <div class="value" id="digOrders">0</div>
    </div>
    <div class="stat-card">
      <h3>Files Processed</h3>
      <div class="value" id="filesProcessed">0</div>
    </div>
    <div class="stat-card">
      <h3>Pending Approvals</h3>
      <div class="value" id="pendingApprovals">0</div>
    </div>
  </div>

  <div class="approvals-section">
    <h2>💬 Slack #graphics-needed Channel</h2>
    <p style="color: #666; margin-bottom: 15px;">Recent messages and requests from the graphics team</p>
    <div id="slackMessages">
      <p style="color: #888;">Loading Slack messages...</p>
    </div>
    <div style="margin-top: 15px;">
      <input type="text" id="slackMessageInput" placeholder="Type a message to #graphics-needed..."
        style="width: calc(100% - 120px); padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px; margin-right: 10px;">
      <button onclick="sendSlackMessage()" style="background: #4facfe; color: white; padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
        📤 Send
      </button>
    </div>
  </div>

  <div class="approvals-section">
    <h2>📁 Google Drive - Digital Samples Library</h2>
    <p style="color: #666; margin-bottom: 15px;">Browse and attach sample files from Google Drive</p>

    <div style="display: flex; gap: 10px; margin-bottom: 15px;">
      <input type="text" id="driveSearchInput" placeholder="Search files by name or SKU..."
        style="flex: 1; padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px;">
      <select id="driveFilterType" style="padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px;">
        <option value="all">All Files</option>
        <option value="images">Images Only</option>
        <option value="pdfs">PDFs Only</option>
      </select>
      <button onclick="refreshDriveFiles()" style="background: #4facfe; color: white; padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
        🔄 Refresh
      </button>
    </div>

    <div id="driveFilesList" style="max-height: 500px; overflow-y: auto;">
      <p style="color: #888;">Loading Google Drive files...</p>
    </div>

    <div id="selectedFilesPanel" style="display: none; margin-top: 20px; padding: 20px; background: #e8f5e9; border-radius: 8px; border-left: 4px solid #4CAF50;">
      <h3 style="margin: 0 0 15px 0; color: #2E7D32;">📎 Selected Files (<span id="selectedCount">0</span>)</h3>
      <div id="selectedFilesList"></div>
      <div style="margin-top: 15px; display: flex; gap: 10px;">
        <select id="attachToOrder" style="flex: 1; padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px;">
          <option value="">Select an order to attach files...</option>
        </select>
        <button onclick="attachFilesToOrder()" style="background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
          ✅ Attach to Order
        </button>
        <button onclick="clearSelectedFiles()" style="background: #f44336; color: white; padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
          ❌ Clear
        </button>
      </div>
    </div>
  </div>

  <div class="approvals-section">
    <h2>📋 Pending Approvals</h2>
    <div id="approvalsList">
      <p style="color: #888;">No pending approvals</p>
    </div>
  </div>

  <div class="approvals-section">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
      <h2 style="margin: 0;">📦 All Samples by Date</h2>
      <div style="display: flex; gap: 10px; align-items: center;">
        <span id="lastRefresh" style="color: #888; font-size: 0.9em;">Last updated: --:--</span>
        <button onclick="manualRefresh()" class="btn" style="background: #4facfe; color: white; padding: 8px 16px; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
          🔄 Refresh Now
        </button>
      </div>
    </div>
    <div style="margin-bottom: 15px;">
      <input
        type="text"
        id="searchSamples"
        placeholder="Search by customer, SKU, or order ID..."
        style="width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px;"
      >
    </div>
    <div id="allSamplesList">
      <p style="color: #888;">Loading samples history...</p>
    </div>
  </div>

  <div class="log-section">
    <h2>📝 Activity Log</h2>
    <div id="activityLog"></div>
  </div>

  <script>
    // Clean SKU - extract only DIG-number (e.g., "DIG-25-some-extra" becomes "DIG-25")
    function cleanSku(sku) {
      if (!sku) return '';
      // Match DIG- followed by digits, ignore everything after
      const match = sku.match(/DIG-\d+/i);
      return match ? match[0] : sku;
    }

    async function updateDashboard() {
      try {
        const statsData = await fetch('/api/stats').then(r => r.json());

        document.getElementById('totalOrders').textContent = statsData.totalOrders;
        document.getElementById('digOrders').textContent = statsData.digOrdersFound;
        document.getElementById('filesProcessed').textContent = statsData.filesProcessed;
        document.getElementById('pendingApprovals').textContent = statsData.pendingApprovals.length;

        // Update approvals list
        const approvalsList = document.getElementById('approvalsList');
        if (statsData.pendingApprovals.length === 0) {
          approvalsList.innerHTML = '<p style="color: #888;">No pending approvals</p>';
        } else {
          approvalsList.innerHTML = statsData.pendingApprovals.map(approval => \`
            <div class="approval-card">
              <h3>\${approval.productTitle} (SKU: \${cleanSku(approval.digSku)})</h3>
              <p><strong>Customer:</strong> \${approval.customerName} (\${approval.customerEmail})</p>
              <p><strong>Order ID:</strong> \${approval.orderId}</p>
              <p><strong>Time:</strong> \${new Date(approval.timestamp).toLocaleString()}</p>
              <img src="\${approval.resizedImageUrl || approval.imageUrl}" alt="Product">
              <div>
                <button class="btn btn-approve" onclick="approveOrder('\${approval.id}')">✅ Approve & Send</button>
                <button class="btn btn-reject" onclick="rejectOrder('\${approval.id}')">❌ Reject</button>
              </div>
            </div>
          \`).join('');
        }

        // Update Slack messages
        const slackData = await fetch('/api/slack/messages').then(r => r.json());
        const slackDiv = document.getElementById('slackMessages');
        if (slackData.messages && slackData.messages.length > 0) {
          slackDiv.innerHTML = slackData.messages.map(msg => \`
            <div style="background: #f8f9fa; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #4facfe;">
              <div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
                <strong style="color: #333;">\${msg.user}</strong>
                <span style="color: #888; font-size: 0.85em;">\${new Date(msg.timestamp * 1000).toLocaleString()}</span>
              </div>
              <div style="color: #555;">\${msg.text}</div>
            </div>
          \`).join('');
        } else {
          slackDiv.innerHTML = '<p style="color: #888;">No recent messages in #graphics-needed</p>';
        }

        // Update all samples list
        const samplesData = await fetch('/api/samples/all').then(r => r.json());
        window.allSamplesData = samplesData.samples || [];
        renderSamples(window.allSamplesData, samplesData);

        // Update activity log
        const logs = await fetch('/api/activity').then(r => r.json());
        const logDiv = document.getElementById('activityLog');
        logDiv.innerHTML = logs.map(log => \`
          <div class="log-entry \${log.type}">
            [\${new Date(log.timestamp).toLocaleTimeString()}] \${log.message}
          </div>
        \`).join('');

        updateLastRefreshTime();
      } catch (error) {
        console.error('Update error:', error);
      }
    }

    // Render samples function
    function renderSamples(samples, data) {
      const samplesList = document.getElementById('allSamplesList');
      if (samples && samples.length > 0) {
        const totalCount = data.total || samples.length;
        const last90Count = data.last90Days || samples.filter(s => {
          const daysAgo = (Date.now() - new Date(s.timestamp).getTime()) / (1000 * 60 * 60 * 24);
          return daysAgo <= 90;
        }).length;

        samplesList.innerHTML = \`
          <div style="background: #e3f2fd; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #2196F3;">
            <h3 style="margin: 0 0 10px 0; color: #1976D2;">📊 Summary</h3>
            <p style="margin: 5px 0;"><strong>DIG Samples (Last 90 Days):</strong> \${last90Count}</p>
            <p style="margin: 5px 0;"><strong>Total Displayed:</strong> \${samples.length}</p>
          </div>
        \` + samples.map(sample => {
          const statusColor = sample.status === 'approved' ? '#4CAF50' :
                              sample.status === 'pending' ? '#FFA500' :
                              sample.status === 'ordered' ? '#2196F3' : '#f44336';
          const statusText = sample.status === 'ordered' ? 'ORDERED' : sample.status.toUpperCase();
          const orderLink = sample.orderUrl ?
            \`<a href="\${sample.orderUrl}" target="_blank" style="color: #4facfe; text-decoration: none; font-weight: 600;">🔗 View Order in Shopify →</a>\` :
            '';

          return \`
            <div class="approval-card" style="opacity: \${sample.status === 'approved' || sample.status === 'ordered' ? '0.85' : '1'}; position: relative;">
              <h3>\${sample.productTitle}</h3>
              <p><strong>SKU:</strong> <span style="background: #f0f0f0; padding: 3px 8px; border-radius: 4px; font-family: monospace;">\${cleanSku(sample.digSku)}</span></p>
              <p><strong>Customer:</strong> \${sample.customerName} (\${sample.customerEmail})</p>
              <p><strong>Order:</strong> \${sample.orderId || sample.orderNumber || 'N/A'}</p>
              <p><strong>Status:</strong> <span style="color: \${statusColor}; font-weight: bold; background: \${statusColor}22; padding: 3px 10px; border-radius: 4px;">\${statusText}</span></p>
              <p><strong>Date:</strong> \${new Date(sample.timestamp).toLocaleString()}</p>
              \${orderLink ? \`<p style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e0e0e0;">\${orderLink}</p>\` : ''}
            </div>
          \`;
        }).join('');
      } else {
        samplesList.innerHTML = '<p style="color: #888;">No samples found</p>';
      }
    }

    // Search functionality
    window.allSamplesData = [];
    document.getElementById('searchSamples').addEventListener('input', function(e) {
      const searchTerm = e.target.value.toLowerCase();

      if (!searchTerm) {
        // Show all samples when search is empty
        fetch('/api/samples/all')
          .then(r => r.json())
          .then(data => {
            window.allSamplesData = data.samples || [];
            renderSamples(window.allSamplesData, data);
          });
        return;
      }

      const filtered = window.allSamplesData.filter(sample =>
        (sample.customerName && sample.customerName.toLowerCase().includes(searchTerm)) ||
        (sample.customerEmail && sample.customerEmail.toLowerCase().includes(searchTerm)) ||
        (sample.digSku && sample.digSku.toLowerCase().includes(searchTerm)) ||
        (sample.orderId && sample.orderId.toLowerCase().includes(searchTerm)) ||
        (sample.orderNumber && sample.orderNumber.toLowerCase().includes(searchTerm)) ||
        (sample.productTitle && sample.productTitle.toLowerCase().includes(searchTerm))
      );

      renderSamples(filtered, { total: filtered.length, last90Days: filtered.length });
    });

    function approveOrder(id) {
      fetch(\`/api/approve/\${id}\`, { method: 'POST' })
        .then(r => r.json())
        .then(data => {
          alert(data.message);
          updateDashboard();
        });
    }

    function rejectOrder(id) {
      fetch(\`/api/reject/\${id}\`, { method: 'POST' })
        .then(r => r.json())
        .then(data => {
          alert(data.message);
          updateDashboard();
        });
    }

    async function sendSlackMessage() {
      const input = document.getElementById('slackMessageInput');
      const message = input.value.trim();

      if (!message) {
        alert('Please enter a message');
        return;
      }

      try {
        const response = await fetch('/api/slack/send', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ message })
        });

        const data = await response.json();
        if (data.success) {
          input.value = '';
          updateDashboard(); // Refresh to show new message
        } else {
          alert('Failed to send message: ' + data.error);
        }
      } catch (error) {
        alert('Error sending message');
      }
    }

    function manualRefresh() {
      const button = event.target;
      button.disabled = true;
      button.textContent = '⏳ Refreshing...';

      updateDashboard().then(() => {
        button.disabled = false;
        button.textContent = '🔄 Refresh Now';
      });
    }

    function updateLastRefreshTime() {
      const now = new Date();
      const timeStr = now.toLocaleTimeString();
      document.getElementById('lastRefresh').textContent = \`Last updated: \${timeStr}\`;
    }

    // Auto-refresh every 30 seconds
    setInterval(() => {
      updateDashboard();
      updateLastRefreshTime();
    }, 30000);

    // Initial load
    updateDashboard();
    updateLastRefreshTime();
  </script>
</body>
</html>
  `);
});

// API endpoints
app.get('/api/stats', requireAuth, (req, res) => {
  res.json(stats);
});

app.get('/api/activity', requireAuth, (req, res) => {
  res.json(activityLog.slice(0, 50));
});

// Get Slack channel ID
async function getGraphicsNeededChannelId() {
  try {
    const result = await slackClient.conversations.list({
      types: 'public_channel,private_channel',
      limit: 200
    });

    const channel = result.channels?.find((c: any) => c.name === 'graphics-needed');
    if (channel) {
      return channel.id;
    }

    console.error('Channel #graphics-needed not found in:', result.channels?.map((c: any) => c.name));
    return null;
  } catch (error) {
    console.error('Error getting channel list:', error);
    return null;
  }
}

// Slack API endpoints
app.get('/api/slack/messages', requireAuth, async (req, res) => {
  try {
    const channelId = await getGraphicsNeededChannelId();
    if (!channelId) {
      return res.json({ messages: [], error: 'Channel #graphics-needed not found' });
    }

    const result = await slackClient.conversations.history({
      channel: channelId,
      limit: 20
    });

    const messages = result.messages?.map((msg: any) => ({
      user: msg.user || 'Unknown',
      text: msg.text,
      timestamp: msg.ts
    })) || [];

    res.json({ messages });
  } catch (error) {
    console.error('Slack fetch error:', error);
    res.json({ messages: [], error: String(error) });
  }
});

app.post('/api/slack/send', requireAuth, async (req, res) => {
  try {
    const { message } = req.body;

    const channelId = await getGraphicsNeededChannelId();
    if (!channelId) {
      return res.json({ success: false, error: 'Channel #graphics-needed not found' });
    }

    // Try to join the channel first if we get not_in_channel error
    try {
      await slackClient.chat.postMessage({
        channel: channelId,
        text: message,
        unfurl_links: false,
        unfurl_media: false
      });
      res.json({ success: true });
    } catch (postError: any) {
      // If not in channel, try to join it
      if (postError.data?.error === 'not_in_channel') {
        console.log('🔧 Bot not in channel, attempting to join #graphics-needed...');
        try {
          await slackClient.conversations.join({ channel: channelId });
          console.log('✅ Successfully joined #graphics-needed');

          // Retry posting message
          await slackClient.chat.postMessage({
            channel: channelId,
            text: message,
            unfurl_links: false,
            unfurl_media: false
          });
          res.json({ success: true });
        } catch (joinError) {
          console.error('❌ Failed to join channel:', joinError);
          res.json({ success: false, error: 'Bot needs to be invited to #graphics-needed channel by a workspace admin' });
        }
      } else {
        throw postError;
      }
    }
  } catch (error) {
    console.error('Slack send error:', error);
    res.json({ success: false, error: String(error) });
  }
});

// Keep track of all samples history (approved + pending)
const samplesHistory: Array<{
  id: string;
  orderId: string;
  customerEmail: string;
  customerName: string;
  digSku: string;
  productTitle: string;
  imageUrl: string;
  status: 'pending' | 'approved' | 'rejected';
  timestamp: Date;
  processedAt?: Date;
}> = [];

// Get all samples (pending + approved history + last 90 days from Shopify)
app.get('/api/samples/all', requireAuth, async (req, res) => {
  try {
    // Combine current pending with history
    const currentPending = stats.pendingApprovals.map(p => ({
      ...p,
      status: p.status as 'pending' | 'approved' | 'rejected',
      orderUrl: `https://admin.shopify.com/store/${SHOPIFY_STORE.replace('.myshopify.com', '')}/orders`
    }));

    // Fetch orders from last 90 days from Shopify
    const ninetyDaysAgo = new Date();
    ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);

    const shopifyUrl = `https://${SHOPIFY_STORE}/admin/api/${API_VERSION}/orders.json?status=any&created_at_min=${ninetyDaysAgo.toISOString()}&limit=250`;

    const response = await fetch(shopifyUrl, {
      headers: {
        'X-Shopify-Access-Token': SHOPIFY_TOKEN,
      },
    });

    const shopifyData: any = await response.json();
    const orders = shopifyData.orders || [];

    addLog(`Fetched ${orders.length} orders from Shopify (last 90 days)`, 'processing');

    // Extract DIG samples from Shopify orders
    const shopifySamples: Array<any> = [];

    for (const order of orders) {
      const digItems = order.line_items?.filter((item: any) =>
        item.sku && item.sku.toUpperCase().startsWith('DIG-')
      ) || [];

      if (digItems.length > 0) {
        const customerName = `${order.customer?.first_name || ''} ${order.customer?.last_name || ''}`.trim() || 'Customer';
        const customerEmail = order.customer?.email || 'No email';

        for (const item of digItems) {
          shopifySamples.push({
            id: `shopify-${order.id}-${item.id}`,
            orderId: order.name || `#${order.order_number}`,
            orderNumber: order.order_number.toString(),
            shopifyOrderId: order.id.toString(),
            customerEmail,
            customerName,
            digSku: item.sku,
            productTitle: item.title,
            imageUrl: item.properties?.find((p: any) => p.name === 'Image')?.value || '',
            status: 'ordered',
            timestamp: new Date(order.created_at),
            orderUrl: `https://admin.shopify.com/store/${SHOPIFY_STORE.replace('.myshopify.com', '')}/orders/${order.id}`,
            financialStatus: order.financial_status,
            fulfillmentStatus: order.fulfillment_status || 'unfulfilled'
          });
        }
      }
    }

    addLog(`Found ${shopifySamples.length} DIG samples in recent orders`, 'processing');

    const allSamples = [...samplesHistory, ...currentPending, ...shopifySamples].sort((a, b) =>
      new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
    );

    res.json({ samples: allSamples, total: allSamples.length, last90Days: shopifySamples.length });
  } catch (error) {
    console.error('Error fetching samples:', error);
    addLog(`Error fetching Shopify orders: ${error}`, 'error');

    // Fallback to local history only
    const currentPending = stats.pendingApprovals.map(p => ({
      ...p,
      status: p.status as 'pending' | 'approved' | 'rejected'
    }));

    const allSamples = [...samplesHistory, ...currentPending].sort((a, b) =>
      new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
    );

    res.json({ samples: allSamples, total: allSamples.length });
  }
});

app.post('/api/approve/:id', requireAuth, async (req, res) => {
  const approval = stats.pendingApprovals.find(a => a.id === req.params.id);
  if (!approval) {
    return res.status(404).json({ error: 'Approval not found' });
  }

  approval.status = 'approved';

  try {
    // Send to Slack #new-products channel
    await slackClient.chat.postMessage({
      channel: SLACK_CHANNEL,
      text: `🆕 *DIG Sample Approved & Sent*\n\n📦 *${approval.productTitle}*\n• SKU: \`${approval.digSku}\`\n• Customer: ${approval.customerName}\n• Order: ${approval.orderId}\n\n✅ Sample sent to customer!`,
      unfurl_links: false,
      unfurl_media: false
    });

    stats.slackPostsSent++;
    stats.emailsSent++;
    addLog(`Approved and sent DIG-${approval.digSku} to ${approval.customerName}`, 'email');

    // Add to history before removing from pending
    samplesHistory.push({
      ...approval,
      status: 'approved',
      processedAt: new Date()
    });

    // Remove from pending
    stats.pendingApprovals = stats.pendingApprovals.filter(a => a.id !== req.params.id);

    res.json({ success: true, message: 'Sample approved and sent to customer!' });
  } catch (error) {
    addLog(`Error sending approval: ${error}`, 'error');
    res.status(500).json({ error: 'Failed to send sample' });
  }
});

app.post('/api/reject/:id', requireAuth, (req, res) => {
  const approval = stats.pendingApprovals.find(a => a.id === req.params.id);
  if (approval) {
    // Add to history before removing
    samplesHistory.push({
      ...approval,
      status: 'rejected',
      processedAt: new Date()
    });
  }

  stats.pendingApprovals = stats.pendingApprovals.filter(a => a.id !== req.params.id);
  addLog(`Rejected DIG sample order ${req.params.id}`, 'processing');
  res.json({ success: true, message: 'Order rejected' });
});

// Google Drive API endpoints
app.get('/api/drive/search', requireAuth, async (req, res) => {
  try {
    const query = (req.query.q as string) || '';
    const files = await searchDriveFiles(query);

    res.json({
      success: true,
      files: files.map((f: any) => ({
        id: f.id,
        name: f.name,
        mimeType: f.mimeType,
        link: f.webViewLink,
        thumbnail: f.thumbnailLink,
        size: f.size,
        modified: f.modifiedTime
      })),
      total: files.length
    });
  } catch (error) {
    res.status(500).json({ error: `Drive search failed: ${error}` });
  }
});

app.get('/api/drive/file/:id', requireAuth, async (req, res) => {
  try {
    const fileInfo = await getDriveFileLink(req.params.id);
    res.json({ success: true, file: fileInfo });
  } catch (error) {
    res.status(500).json({ error: `Failed to get file: ${error}` });
  }
});

// Shopify webhook endpoint (receives new orders)
app.post('/webhook/shopify/orders/create', async (req, res) => {
  try {
    const order = req.body;
    stats.totalOrders++;

    addLog(`New order received: #${order.order_number}`, 'order');

    // Check for DIG-series items
    const digItems = order.line_items?.filter((item: any) =>
      item.sku && item.sku.toUpperCase().startsWith('DIG-')
    ) || [];

    if (digItems.length > 0) {
      stats.digOrdersFound++;

      const customerName = `${order.customer?.first_name || ''} ${order.customer?.last_name || ''}`.trim() || 'Customer';
      const customerEmail = order.customer?.email || '';

      stats.lastOrder = {
        orderId: order.name || order.order_number.toString(),
        customerName,
        digItems: digItems.map((item: any) => item.sku),
        timestamp: new Date()
      };

      addLog(`Found ${digItems.length} DIG items in order #${order.order_number}`, 'processing');

      // Process each DIG item
      for (const item of digItems) {
        await processDIGItem(order, item, customerEmail, customerName);
      }
    }

    res.json({ success: true });
  } catch (error) {
    addLog(`Webhook error: ${error}`, 'error');
    res.status(500).json({ error: 'Webhook processing failed' });
  }
});

// Google Drive search function
async function searchDriveFiles(query: string = '') {
  try {
    const response = await drive.files.list({
      q: query ? `name contains '${query}' and mimeType contains 'image/'` : "mimeType contains 'image/'",
      fields: 'files(id, name, mimeType, webViewLink, thumbnailLink, size, createdTime, modifiedTime)',
      orderBy: 'modifiedTime desc',
      pageSize: 50
    });

    return response.data.files || [];
  } catch (error) {
    addLog(`Error searching Drive: ${error}`, 'error');
    return [];
  }
}

// Get Drive file download link
async function getDriveFileLink(fileId: string) {
  try {
    const response = await drive.files.get({
      fileId,
      fields: 'webContentLink, webViewLink'
    });
    return response.data;
  } catch (error) {
    addLog(`Error getting Drive file link: ${error}`, 'error');
    return null;
  }
}

async function processDIGItem(order: any, item: any, customerEmail: string, customerName: string) {
  try {
    stats.filesProcessed++;

    // Search Google Drive for matching files
    const sku = item.sku.replace('DIG-', '');
    const driveFiles = await searchDriveFiles(sku);

    addLog(`Found ${driveFiles.length} Drive files matching SKU: ${sku}`, 'processing');

    // Create approval entry
    const approval = {
      id: `${order.id}-${item.id}-${Date.now()}`,
      orderId: order.name || order.order_number.toString(),
      customerEmail,
      customerName,
      digSku: item.sku,
      productTitle: item.title,
      imageUrl: item.properties?.find((p: any) => p.name === 'Image')?.value || '',
      resizedImageUrl: '', // Will be populated after image processing
      driveFiles: driveFiles.map((f: any) => ({
        id: f.id,
        name: f.name,
        link: f.webViewLink,
        thumbnail: f.thumbnailLink
      })),
      status: 'pending' as const,
      timestamp: new Date()
    };

    stats.pendingApprovals.push(approval);

    addLog(`Created approval for ${item.sku} - ${customerName} with ${driveFiles.length} Drive files`, 'processing');

  } catch (error) {
    addLog(`Error processing DIG item: ${error}`, 'error');
  }
}

// Manual test endpoint
app.post('/api/test/dig-order', requireAuth, async (req, res) => {
  const mockOrder = {
    id: Date.now(),
    order_number: 12345,
    name: '#TEST-' + Date.now(),
    customer: {
      first_name: 'Test',
      last_name: 'Customer',
      email: 'test@example.com'
    },
    line_items: [
      {
        id: Date.now(),
        sku: 'DIG-12345',
        title: 'Digital Sample - Test Pattern',
        properties: [
          { name: 'Image', value: 'https://via.placeholder.com/800x600' }
        ]
      }
    ]
  };

  await processDIGItem(mockOrder, mockOrder.line_items[0], 'test@example.com', 'Test Customer');

  res.json({ success: true, message: 'Test DIG order created!' });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log('');
  console.log('📦 Digital Samples Agent');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('🌍 External: http://45.61.58.125:' + PORT);
  console.log('🏠 Local: http://localhost:' + PORT);
  console.log('');
  console.log('✅ Monitoring Shopify for DIG-series orders...');
  console.log('📁 Google Drive integration: Ready');
  console.log('🖼️  Image resizing: 24" @ 150 DPI');
  console.log('📢 Slack channel: #new-products');
  console.log('');
  addLog('Digital Samples Agent initialized and running', 'processing');
});