import crypto from 'crypto';
import type { RowDataPacket } from 'mysql2/promise';
import { getPool, isPoolReady } from './db.js';

const AUTH_CODE_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes
const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours

export interface Client {
  client_id: string;
  client_secret: string;
  client_name: string;
  redirect_uris: string[];
  created_at: number;
}

interface AuthCode {
  code: string;
  client_id: string;
  redirect_uri: string;
  scope?: string;
  expires_at: number;
}

interface Token {
  access_token: string;
  token_type: string;
  expires_in: number;
  scope?: string;
  expires_at: number;
}

// Cleanup expired rows every 60 seconds
setInterval(async () => {
  if (!isPoolReady()) return;
  try {
    const pool = getPool();
    await pool.execute('DELETE FROM oauth_auth_codes WHERE expires_at < NOW() OR used = TRUE');
    await pool.execute('DELETE FROM oauth_tokens WHERE expires_at < NOW()');
  } catch (err) {
    console.error('[oauth] Cleanup error:', err);
  }
}, 60_000);

export function generateClientId(): string {
  return crypto.randomUUID();
}

export function generateClientSecret(): string {
  return crypto.randomBytes(32).toString('hex');
}

export function generateAuthCode(): string {
  return crypto.randomBytes(32).toString('hex');
}

export function generateAccessToken(): string {
  return crypto.randomBytes(32).toString('hex');
}

export async function registerClient(body: {
  client_name?: string;
  redirect_uris?: string[];
  [key: string]: unknown;
}): Promise<Client> {
  const client: Client = {
    client_id: generateClientId(),
    client_secret: generateClientSecret(),
    client_name: body.client_name || 'Unnamed Client',
    redirect_uris: Array.isArray(body.redirect_uris) ? body.redirect_uris : [],
    created_at: Date.now(),
  };

  const pool = getPool();
  await pool.execute(
    'INSERT INTO oauth_clients (client_id, client_secret, client_name, redirect_urls, grant_types) VALUES (?, ?, ?, ?, ?)',
    [
      client.client_id,
      client.client_secret,
      client.client_name,
      JSON.stringify(client.redirect_uris),
      JSON.stringify(['authorization_code']),
    ]
  );

  console.log(`[oauth] Registered client ${client.client_id} (${client.client_name})`);
  return client;
}

export async function getClient(clientId: string): Promise<Client | undefined> {
  try {
    const pool = getPool();
    const [rows] = await pool.execute<RowDataPacket[]>(
      'SELECT * FROM oauth_clients WHERE client_id = ?',
      [clientId]
    );

    if (!Array.isArray(rows) || rows.length === 0) {
      return undefined;
    }

    const row = rows[0];

    // Update last_used
    await pool.execute(
      'UPDATE oauth_clients SET last_used = CURRENT_TIMESTAMP WHERE client_id = ?',
      [clientId]
    );

    return {
      client_id: row.client_id,
      client_secret: row.client_secret,
      client_name: row.client_name,
      redirect_uris: Array.isArray(row.redirect_urls) ? row.redirect_urls : JSON.parse(row.redirect_urls || '[]'),
      created_at: new Date(row.created_at).getTime(),
    };
  } catch (err) {
    console.error('[oauth] getClient error:', err);
    return undefined;
  }
}

export async function createAuthCode(
  clientId: string,
  redirectUri: string,
  scope?: string
): Promise<AuthCode> {
  const code: AuthCode = {
    code: generateAuthCode(),
    client_id: clientId,
    redirect_uri: redirectUri,
    scope,
    expires_at: Date.now() + AUTH_CODE_EXPIRY_MS,
  };

  const pool = getPool();
  await pool.execute(
    'INSERT INTO oauth_auth_codes (code, client_id, redirect_uri, expires_at) VALUES (?, ?, ?, ?)',
    [code.code, clientId, redirectUri, new Date(code.expires_at)]
  );

  console.log(`[oauth] Created auth code ${code.code.slice(0, 8)}... for client ${clientId}`);
  return code;
}

export async function exchangeCodeForToken(
  clientId: string,
  clientSecret: string,
  code: string,
  redirectUri: string
): Promise<Token | null> {
  let client: Client | undefined;
  try {
    client = await getClient(clientId);
  } catch (err) {
    console.error('[oauth] getClient error during token exchange:', err);
    return null;
  }

  if (!client || client.client_secret !== clientSecret) {
    console.log('[oauth] Invalid client credentials');
    return null;
  }

  const pool = getPool();
  const db = await pool.getConnection();
  try {
    const [rows] = await db.execute<RowDataPacket[]>(
      'SELECT * FROM oauth_auth_codes WHERE code = ? AND used = FALSE AND expires_at > NOW()',
      [code]
    );

    if (!Array.isArray(rows) || rows.length === 0) {
      console.log('[oauth] Auth code not found or expired');
      return null;
    }

    const authCode = rows[0];
    if (authCode.client_id !== clientId || authCode.redirect_uri !== redirectUri) {
      console.log('[oauth] Auth code client/redirect mismatch');
      return null;
    }

    // Mark auth code as used
    await db.execute('UPDATE oauth_auth_codes SET used = TRUE WHERE code = ?', [code]);

    const token: Token = {
      access_token: generateAccessToken(),
      token_type: 'Bearer',
      expires_in: TOKEN_EXPIRY_MS / 1000,
      scope: authCode.scope || undefined,
      expires_at: Date.now() + TOKEN_EXPIRY_MS,
    };

    await db.execute(
      'INSERT INTO oauth_tokens (token, client_id, token_type, expires_at) VALUES (?, ?, ?, ?)',
      [token.access_token, clientId, 'access', new Date(token.expires_at)]
    );

    console.log(`[oauth] Issued token ${token.access_token.slice(0, 8)}... for client ${clientId}`);
    return token;
  } catch (err) {
    console.error('[oauth] exchangeCodeForToken error:', err);
    return null;
  } finally {
    db.release();
  }
}

export async function validateAccessToken(tokenValue: string): Promise<boolean> {
  try {
    const pool = getPool();
    const [rows] = await pool.execute<RowDataPacket[]>(
      'SELECT * FROM oauth_tokens WHERE token = ? AND expires_at > NOW()',
      [tokenValue]
    );

    if (!Array.isArray(rows) || rows.length === 0) {
      return false;
    }

    return true;
  } catch (err) {
    console.error('[oauth] validateAccessToken error:', err);
    return false;
  }
}

export function getAuthorizeHtml(params: {
  client_id: string;
  redirect_uri: string;
  state?: string;
  scope?: string;
}): string {
  const { client_id, redirect_uri, state, scope } = params;
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Authorize Hermes MCP</title>
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }
    .card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 400px; width: 100%; }
    h1 { margin: 0 0 1rem; font-size: 1.5rem; color: #111; }
    p { color: #555; line-height: 1.5; margin: 0.5rem 0; }
    .client { font-weight: 600; color: #111; }
    .scopes { background: #f9f9f9; padding: 0.75rem; border-radius: 8px; margin: 1rem 0; font-size: 0.9rem; color: #333; }
    .buttons { display: flex; gap: 0.75rem; margin-top: 1.5rem; }
    button { flex: 1; padding: 0.75rem; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; }
    .allow { background: #111; color: white; }
    .deny { background: #e5e5e5; color: #333; }
  </style>
</head>
<body>
  <div class="card">
    <h1>Authorize Hermes MCP</h1>
    <p>Client <span class="client">${escapeHtml(client_id)}</span> wants to access your Hermes tools (email + Obsidian vault).</p>
    <div class="scopes">Scopes: ${escapeHtml(scope || 'default')}</div>
    <form method="POST" action="/oauth/authorize">
      <input type="hidden" name="client_id" value="${escapeHtml(client_id)}">
      <input type="hidden" name="redirect_uri" value="${escapeHtml(redirect_uri)}">
      ${state ? `<input type="hidden" name="state" value="${escapeHtml(state)}">` : ''}
      ${scope ? `<input type="hidden" name="scope" value="${escapeHtml(scope)}">` : ''}
      <div class="buttons">
        <button type="submit" name="action" value="deny" class="deny">Deny</button>
        <button type="submit" name="action" value="allow" class="allow">Allow</button>
      </div>
    </form>
  </div>
</body>
</html>`;
}

function escapeHtml(text: string): string {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}
