import express from 'express';
import { Provider } from 'oidc-provider';
import { nip19, getEventHash, verifyEvent, finalizeEvent, getPublicKey } from 'nostr-tools';
import crypto from 'crypto';

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

// In-memory storage for demo purposes
const users = new Map();
const sessions = new Map();
const authCodes = new Map();

// Production OIDC configuration
const configuration = {
  clients: [{
    client_id: 'GITEA',
    client_secret: 'gitea-secret',
    redirect_uris: ['https://git.laantungir.net/user/oauth2/NOSTR/callback'],
    response_types: ['code'],
    grant_types: ['authorization_code'],
    scope: 'openid profile email',
  }],
  claims: {
    openid: ['sub'],
    profile: ['name', 'preferred_username'],
    email: ['email'],
  },
  // Add proxy-related settings
  cookies: {
    secure: true,
    sameSite: 'none'
  },
  features: {
    devInteractions: { enabled: false },
    introspection: { enabled: true },
    revocation: { enabled: true },
  },
  ttl: {
    AccessToken: 1 * 60 * 60, // 1 hour
    AuthorizationCode: 10 * 60, // 10 minutes
    IdToken: 1 * 60 * 60, // 1 hour
    DeviceCode: 10 * 60, // 10 minutes
    RefreshToken: 1 * 24 * 60 * 60, // 1 day
  },
  issueRefreshToken: async (ctx, token, client) => {
    return client.grantTypes.includes('refresh_token') && token.scopes.has('offline_access');
  },
  interactions: {
    url: async (ctx, interaction) => {
      return `/interaction/${interaction.uid}`;
    },
  },
  findAccount: async (ctx, sub, token) => {
    const user = users.get(sub);
    if (!user) return undefined;
    
    return {
      accountId: sub,
      claims: async (use, scope) => {
        return {
          sub: user.pubkey,
          name: user.name || user.pubkey.slice(0, 16),
          preferred_username: user.name || user.pubkey.slice(0, 16),
          email: user.email || `${user.pubkey.slice(0, 16)}@nostr.local`,
        };
      },
    };
  },
};

// Initialize OIDC provider with production issuer
const oidc = new Provider('https://auth.laantungir.net', configuration);

// Add middleware to handle proxy forwarding
oidc.use(async (ctx, next) => {
  ctx.secure = true;  // Force secure context
  ctx.protocol = 'https';  // Force HTTPS protocol
  await next();
});

// Custom interaction handling
oidc.use(async (ctx, next) => {
  if (ctx.path.startsWith('/interaction/')) {
    const uid = ctx.path.split('/')[2];
    const interaction = await oidc.interactionDetails(ctx.req, ctx.res);
    
    if (ctx.method === 'GET') {
      // Show login form
      const challenge = crypto.randomBytes(32).toString('hex');
      sessions.set(uid, { challenge, interaction });
      
      // Render login page
      ctx.type = 'html';
      ctx.body = await renderLoginPage(challenge, uid, interaction);
      return;
    }
    
    if (ctx.method === 'POST') {
      // Handle login submission
      const body = await parseBody(ctx);
      const sessionData = sessions.get(uid);
      
      if (!sessionData) {
        ctx.status = 400;
        ctx.body = { error: 'Session expired' };
        return;
      }
      
      try {
        let nostrEvent;
        
        if (body.signature && body.pubkey) {
          // Manual signing
          nostrEvent = {
            kind: 1,
            pubkey: body.pubkey,
            created_at: Math.floor(Date.now() / 1000),
            tags: [],
            content: `Authenticating to ${sessionData.challenge}`,
            id: '',
            sig: body.signature
          };
          nostrEvent.id = getEventHash(nostrEvent);
        } else if (body.event) {
          // Extension signing
          nostrEvent = JSON.parse(body.event);
        } else {
          throw new Error('No signature provided');
        }
        
        // Verify the event
        if (!verifyEvent(nostrEvent)) {
          throw new Error('Invalid signature');
        }
        
        // Check if challenge matches
        if (!nostrEvent.content.includes(sessionData.challenge)) {
          throw new Error('Challenge mismatch');
        }
        
        // Create or get user
        const pubkey = nostrEvent.pubkey;
        const npub = nip19.npubEncode(pubkey);
        
        if (!users.has(pubkey)) {
          users.set(pubkey, {
            pubkey,
            npub,
            name: npub.slice(0, 16),
            email: `${pubkey.slice(0, 16)}@nostr.local`,
          });
        }
        
        const user = users.get(pubkey);
        
        // Complete the interaction
        const result = {
          login: {
            account: pubkey,
          },
        };
        
        await oidc.interactionFinished(ctx.req, ctx.res, result, {
          mergeWithLastSubmission: false,
        });
        
        sessions.delete(uid);
        return;
        
      } catch (error) {
        console.error('Authentication error:', error);
        ctx.status = 400;
        ctx.body = { error: error.message };
        return;
      }
    }
  }
  
  await next();
});

// Helper function to parse POST body
async function parseBody(ctx) {
  return new Promise((resolve, reject) => {
    let body = '';
    ctx.req.on('data', chunk => {
      body += chunk.toString();
    });
    ctx.req.on('end', () => {
      try {
        const parsed = new URLSearchParams(body);
        const result = {};
        for (const [key, value] of parsed) {
          result[key] = value;
        }
        resolve(result);
      } catch (error) {
        reject(error);
      }
    });
  });
}

// Helper function to render login page
async function renderLoginPage(challenge, uid, interaction) {
  return `
<!DOCTYPE html>
<html>
<head>
    <title>Nostr OIDC Bridge</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body { 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 500px; 
            margin: 50px auto; 
            padding: 20px;
            background: #f5f5f5;
        }
        .container { 
            background: white; 
            padding: 30px; 
            border-radius: 8px; 
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 { color: #333; text-align: center; margin-bottom: 30px; }
        .method { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
        .method h3 { margin-top: 0; color: #555; }
        button { 
            background: #007bff; 
            color: white; 
            border: none; 
            padding: 10px 20px; 
            border-radius: 5px; 
            cursor: pointer;
            font-size: 16px;
        }
        button:hover { background: #0056b3; }
        input, textarea { 
            width: 100%; 
            padding: 10px; 
            margin: 10px 0; 
            border: 1px solid #ddd; 
            border-radius: 5px;
            font-family: monospace;
        }
        .challenge { 
            background: #f8f9fa; 
            padding: 15px; 
            border-radius: 5px; 
            word-break: break-all;
            font-family: monospace;
            font-size: 14px;
        }
        .error { color: #dc3545; background: #f8d7da; padding: 10px; border-radius: 5px; margin: 10px 0; }
        .success { color: #155724; background: #d4edda; padding: 10px; border-radius: 5px; margin: 10px 0; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🔐 Sign in with Nostr</h1>
        <p>To authenticate, please sign the following challenge with your Nostr key:</p>
        
        <div class="challenge">
            <strong>Challenge:</strong><br>
            Authenticating to ${challenge}
        </div>

        <div class="method">
            <h3>🔌 Method 1: Browser Extension (Recommended)</h3>
            <p>If you have a Nostr browser extension (nos2x, Alby, etc.), click below:</p>
            <button onclick="signWithExtension()">Sign with Extension</button>
            <div id="extension-result"></div>
        </div>

        <div class="method">
            <h3>🔑 Method 2: Manual Signing</h3>
            <p>If you don't have an extension, you can manually sign the challenge:</p>
            
            <form method="POST">
                <label>Your Public Key (hex):</label>
                <input type="text" name="pubkey" placeholder="Enter your public key in hex format" required>
                
                <label>Signature (hex):</label>
                <input type="text" name="signature" placeholder="Enter the signature for the challenge" required>
                
                <button type="submit">Verify Signature</button>
            </form>
        </div>
    </div>

    <script>
        const challenge = "Authenticating to ${challenge}";
        
        async function signWithExtension() {
            const resultDiv = document.getElementById('extension-result');
            
            try {
                if (!window.nostr) {
                    throw new Error('No Nostr extension found. Please install a Nostr browser extension like nos2x or Alby.');
                }

                // Create event to sign
                const event = {
                    kind: 1,
                    created_at: Math.floor(Date.now() / 1000),
                    tags: [],
                    content: challenge,
                };

                // Get public key
                const pubkey = await window.nostr.getPublicKey();
                event.pubkey = pubkey;

                // Sign the event
                const signedEvent = await window.nostr.signEvent(event);
                
                // Submit the signed event
                const formData = new FormData();
                formData.append('event', JSON.stringify(signedEvent));
                
                const response = await fetch('/interaction/${uid}', {
                    method: 'POST',
                    body: formData
                });
                
                if (response.ok) {
                    resultDiv.innerHTML = '<div class="success">✅ Authentication successful! Redirecting...</div>';
                    // The OIDC provider will handle the redirect
                    window.location.reload();
                } else {
                    const error = await response.json();
                    throw new Error(error.error || 'Authentication failed');
                }
                
            } catch (error) {
                console.error('Extension signing error:', error);
                resultDiv.innerHTML = \`<div class="error">❌ \${error.message}</div>\`;
            }
        }
    </script>
</body>
</html>`;
}

// Middleware setup
app.use(express.static('public'));
app.set('trust proxy', true);  // Trust proxy headers
app.use(oidc.callback());

app.listen(PORT, '0.0.0.0', () => {
  console.log(`🚀 Nostr OIDC Bridge running on http://0.0.0.0:${PORT}`);
  console.log(`🔗 Discovery URL: https://auth.laantungir.net/.well-known/openid-configuration`);
});
