import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { env } from '../config/env';
import { hashSha256 } from '../utils/crypto';
import { ApiKeyRepository } from '../models/repositories/ApiKeyRepository';

const apiKeyRepo = new ApiKeyRepository();

interface JwtPayload {
  sub: string;
  purpose?: string;
  provider?: string;
}

function extractToken(req: Request): string | null {
  const header = req.headers.authorization;
  if (header?.startsWith('Bearer ')) {
    return header.slice(7);
  }

  // Browser OAuth redirects cannot attach Authorization headers.
  if (
    req.method === 'GET' &&
    req.path.includes('/calendar-connections/oauth/') &&
    typeof req.query.access_token === 'string' &&
    req.query.access_token.length > 0
  ) {
    return req.query.access_token;
  }

  return null;
}

export async function requireAuth(
  req: Request,
  res: Response,
  next: NextFunction,
): Promise<void> {
  const token = extractToken(req);
  if (!token) {
    deny(req, res, 'Authentication required.');
    return;
  }

  if (token.startsWith('cal_live_')) {
    const keyHash = hashSha256(token);
    const apiKey = await apiKeyRepo.findByHash(keyHash);
    if (!apiKey || (apiKey.expiresAt && apiKey.expiresAt < new Date())) {
      deny(req, res, 'Invalid API key.');
      return;
    }
    req.apiKeyUserId = apiKey.userId;
    req.apiKeyScopes = apiKey.scopes;
    req.userId = apiKey.userId;
    await apiKeyRepo.updateLastUsed(apiKey.id);
    next();
    return;
  }

  try {
    const payload = jwt.verify(token, env.JWT_SECRET) as JwtPayload;
    req.userId = payload.sub;
    next();
  } catch {
    deny(req, res, 'Invalid or expired token.');
  }
}

function deny(req: Request, res: Response, message: string): void {
  if (isDocumentNavigation(req)) {
    res.redirect(loginRedirectUrl(req));
    return;
  }
  res.status(401).json({ error: 'unauthorized', message });
}

function isDocumentNavigation(req: Request): boolean {
  return req.method === 'GET' && req.get('sec-fetch-dest') === 'document';
}

function loginRedirectUrl(req: Request): string {
  const allowed = new Set(env.corsOrigins);
  const referer = req.get('referer');
  if (referer) {
    try {
      const origin = new URL(referer).origin;
      if (allowed.has(origin)) {
        return `${origin}/login?session=expired`;
      }
    } catch {
      // ignore malformed referer
    }
  }
  return `${new URL(env.APP_URL).origin}/login?session=expired`;
}

export function requireScope(scope: string) {
  return (req: Request, res: Response, next: NextFunction): void => {
    if (req.apiKeyScopes && !hasScope(req.apiKeyScopes, scope)) {
      res.status(403).json({ error: 'forbidden', message: 'Insufficient scope.' });
      return;
    }
    next();
  };
}

function hasScope(scopes: string[], required: string): boolean {
  return scopes.includes(required);
}
