import { z } from 'zod';
import jwt from 'jsonwebtoken';
import crypto from 'node:crypto';
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import type { Pool } from 'pg';
import pino from 'pino';

const logger = pino({ name: 'auth' });

export interface AuthPayload {
  tenantId: string;
  userId: string;
  email: string;
  role: 'owner' | 'admin' | 'member' | 'viewer';
}

/**
 * JWT auth middleware. Extracts tenant context from Bearer token.
 * Also supports API key auth via `X-API-Key` header (dd0c_ prefix).
 */
export function registerAuth(app: FastifyInstance, jwtSecret: string, pool: Pool) {
  app.decorateRequest('tenantId', '');
  app.decorateRequest('userId', '');
  app.decorateRequest('userRole', 'viewer');

  app.addHook('onRequest', async (req: FastifyRequest, reply: FastifyReply) => {
    if (req.url === '/health') return;
    if (req.url.startsWith('/webhooks/')) return;
    if (req.url.startsWith('/slack/')) return;
    if (req.url.startsWith('/api/v1/auth/login') || req.url.startsWith('/api/v1/auth/signup')) return;

    const apiKey = req.headers['x-api-key'] as string | undefined;
    const authHeader = req.headers['authorization'];

    if (apiKey) {
      if (!apiKey.startsWith('dd0c_') || apiKey.length !== 37) {
        return reply.status(401).send({ error: 'Invalid API key format' });
      }

      const prefix = apiKey.slice(0, 13); // dd0c_ + 8 hex chars
      const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');

      const result = await pool.query(
        `SELECT ak.tenant_id, ak.user_id, u.role
         FROM api_keys ak JOIN users u ON ak.user_id = u.id
         WHERE ak.key_prefix = $1 AND ak.key_hash = $2 AND ak.revoked = false`,
        [prefix, keyHash],
      );

      if (!result.rows[0]) {
        return reply.status(401).send({ error: 'Invalid API key' });
      }

      (req as any).tenantId = result.rows[0].tenant_id;
      (req as any).userId = result.rows[0].user_id;
      (req as any).userRole = result.rows[0].role;
      return;
    }

    if (authHeader?.startsWith('Bearer ')) {
      const token = authHeader.slice(7);
      try {
        const payload = jwt.verify(token, jwtSecret) as AuthPayload;
        (req as any).tenantId = payload.tenantId;
        (req as any).userId = payload.userId;
        (req as any).userRole = payload.role;
        return;
      } catch {
        return reply.status(401).send({ error: 'Invalid or expired token' });
      }
    }

    return reply.status(401).send({ error: 'Missing authentication' });
  });
}

export function requireRole(req: FastifyRequest, reply: FastifyReply, minRole: AuthPayload['role']): boolean {
  const hierarchy: Record<string, number> = { owner: 4, admin: 3, member: 2, viewer: 1 };
  const userLevel = hierarchy[(req as any).userRole] ?? 0;
  const requiredLevel = hierarchy[minRole] ?? 0;

  if (userLevel < requiredLevel) {
    reply.status(403).send({ error: `Requires ${minRole} role or higher` });
    return false;
  }
  return true;
}

export function signToken(payload: AuthPayload, secret: string, expiresIn = '24h'): string {
  return jwt.sign(payload, secret, { expiresIn });
}

// --- Password hashing (scrypt — no native bcrypt dep needed) ---

async function hashPassword(password: string): Promise<string> {
  const salt = crypto.randomBytes(16).toString('hex');
  return new Promise((resolve, reject) => {
    crypto.scrypt(password, salt, 64, (err, derived) => {
      if (err) reject(err);
      resolve(`${salt}:${derived.toString('hex')}`);
    });
  });
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  const [salt, key] = hash.split(':');
  return new Promise((resolve, reject) => {
    crypto.scrypt(password, salt, 64, (err, derived) => {
      if (err) reject(err);
      resolve(crypto.timingSafeEqual(Buffer.from(key, 'hex'), derived));
    });
  });
}

// --- Auth Routes ---

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

const signupSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  tenant_name: z.string().min(1).max(100),
});

export function registerAuthRoutes(app: FastifyInstance, jwtSecret: string, pool: Pool) {
  app.post('/api/v1/auth/login', async (req, reply) => {
    const body = loginSchema.parse(req.body);

    const result = await pool.query(
      `SELECT u.id, u.tenant_id, u.email, u.password_hash, u.role
       FROM users u WHERE u.email = $1`,
      [body.email],
    );

    const user = result.rows[0];
    if (!user) return reply.status(401).send({ error: 'Invalid credentials' });

    const valid = await verifyPassword(body.password, user.password_hash);
    if (!valid) return reply.status(401).send({ error: 'Invalid credentials' });

    const token = signToken({
      tenantId: user.tenant_id,
      userId: user.id,
      email: user.email,
      role: user.role,
    }, jwtSecret);

    return { token, expires_in: '24h' };
  });

  app.post('/api/v1/auth/signup', async (req, reply) => {
    const body = signupSchema.parse(req.body);

    // Check if email already exists
    const existing = await pool.query('SELECT id FROM users WHERE email = $1', [body.email]);
    if (existing.rows[0]) return reply.status(409).send({ error: 'Email already registered' });

    const passwordHash = await hashPassword(body.password);
    const slug = body.tenant_name.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50);

    const client = await pool.connect();
    try {
      await client.query('BEGIN');

      const tenant = await client.query(
        `INSERT INTO tenants (name, slug) VALUES ($1, $2) RETURNING id`,
        [body.tenant_name, slug],
      );
      const tenantId = tenant.rows[0].id;

      const user = await client.query(
        `INSERT INTO users (tenant_id, email, password_hash, role) VALUES ($1, $2, $3, 'owner') RETURNING id`,
        [tenantId, body.email, passwordHash],
      );

      await client.query('COMMIT');

      const token = signToken({
        tenantId,
        userId: user.rows[0].id,
        email: body.email,
        role: 'owner',
      }, jwtSecret);

      return reply.status(201).send({ token, tenant_id: tenantId, expires_in: '24h' });
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally {
      client.release();
    }
  });

  app.get('/api/v1/auth/me', async (req, reply) => {
    return {
      tenant_id: (req as any).tenantId,
      user_id: (req as any).userId,
      role: (req as any).userRole,
    };
  });

  // Generate API key
  app.post('/api/v1/auth/api-keys', async (req, reply) => {
    const tenantId = (req as any).tenantId;
    const userId = (req as any).userId;

    if (!requireRole(req, reply, 'admin')) return;

    const rawKey = `dd0c_${crypto.randomBytes(16).toString('hex')}`;
    const prefix = rawKey.slice(0, 13);
    const keyHash = crypto.createHash('sha256').update(rawKey).digest('hex');

    await pool.query(
      `INSERT INTO api_keys (tenant_id, user_id, key_prefix, key_hash) VALUES ($1, $2, $3, $4)`,
      [tenantId, userId, prefix, keyHash],
    );

    // Return the raw key ONCE — it's never stored or retrievable again
    return reply.status(201).send({ api_key: rawKey, prefix });
  });
}
