import type { Context, Next } from 'hono';
import { createUserClient, createAdminClient } from '../db/client.js';
import { ensureUserProfile } from '../lib/user-profile.js';
import { hashApiKey } from '../lib/api-key-hash.js';

export interface AuthUser {
  id: string;
  email: string;
  username: string;
  display_name: string | null;
  tier: 'free' | 'pro' | 'team' | 'enterprise';
  trusted: boolean;
  reputation_score: number;
}

export interface AuthContext {
  user: AuthUser | null;
  isAuthenticated: boolean;
  isAdmin: boolean;
  apiKeyId?: string | null;
  apiKeyOrgId?: string | null;
  apiKeyScopes?: string[];
  authSource?: 'jwt' | 'api_key' | null;
}

export const API_KEY_SCOPES = [
  'read',
  'content:write',
  'org:read',
  'org:write',
  'billing:manage',
  'api_keys:manage',
  'admin:*',
] as const;

export type ApiKeyScope = typeof API_KEY_SCOPES[number];

export function isApiKeyScope(scope: unknown): scope is ApiKeyScope {
  return typeof scope === 'string' && (API_KEY_SCOPES as readonly string[]).includes(scope);
}

export async function getAuthContext(c: Context): Promise<AuthContext> {
  const authHeader = c.req.header('Authorization');
  const apiKeyHeader = c.req.header('X-API-Key');

  // Try JWT auth first
  if (authHeader?.startsWith('Bearer ')) {
    const token = authHeader.slice(7);
    const client = createUserClient(token);

    const { data: { user: authUser }, error: authError } = await client.auth.getUser();

    if (!authError && authUser) {
      let { data: profile } = await client
        .from('users')
        .select('*')
        .eq('id', authUser.id)
        .single();

      if (!profile) {
        profile = await ensureUserProfile({
          id: authUser.id,
          email: authUser.email,
          user_metadata: authUser.user_metadata ?? {},
        });
      }

      if (profile) {
        return {
          user: {
            id: profile.id,
            email: profile.email,
            username: profile.username,
            display_name: profile.display_name,
            tier: profile.tier,
            trusted: profile.trusted,
            reputation_score: profile.reputation_score,
          },
          isAuthenticated: true,
          isAdmin: false,
          apiKeyId: null,
          apiKeyOrgId: null,
          apiKeyScopes: [],
          authSource: 'jwt',
        };
      }
    }
  }

  // Try API key auth
  if (apiKeyHeader) {
    const keyHash = hashApiKey(apiKeyHeader);
    const adminClient = createAdminClient();

    const { data: apiKey } = await adminClient
      .from('api_keys')
      .select('*, users(*)')
      .eq('key_hash', keyHash)
      .is('revoked_at', null)
      .single();

    if (apiKey && apiKey.users) {
      // Update last_used_at
      await adminClient
        .from('api_keys')
        .update({ last_used_at: new Date().toISOString() })
        .eq('id', apiKey.id);

      const profile = apiKey.users as unknown as AuthUser;
      const scopes = Array.isArray(apiKey.scopes)
        ? apiKey.scopes.filter(isApiKeyScope)
        : [];
      return {
        user: {
          id: profile.id,
          email: profile.email,
          username: profile.username,
          display_name: profile.display_name,
          tier: profile.tier,
          trusted: profile.trusted,
          reputation_score: profile.reputation_score,
        },
        isAuthenticated: true,
        isAdmin: false,
        apiKeyId: apiKey.id ?? null,
        apiKeyOrgId: apiKey.org_id ?? null,
        apiKeyScopes: scopes,
        authSource: 'api_key',
      };
    }
  }

  return {
    user: null,
    isAuthenticated: false,
    isAdmin: false,
    apiKeyId: null,
    apiKeyOrgId: null,
    apiKeyScopes: [],
    authSource: null,
  };
}

export function requireAuth() {
  return async (c: Context, next: Next) => {
    const auth = await getAuthContext(c);

    if (!auth.isAuthenticated || !auth.user) {
      return c.json({ error: 'Unauthorized' }, 401);
    }

    c.set('auth', auth);
    await next();
  };
}

export function optionalAuth() {
  return async (c: Context, next: Next) => {
    try {
      const auth = await getAuthContext(c);
      c.set('auth', auth);
    } catch {
      // Auth failed (Supabase unavailable, etc.) — continue as unauthenticated
      c.set('auth', {
        user: null,
        isAuthenticated: false,
        isAdmin: false,
        apiKeyId: null,
        apiKeyOrgId: null,
        apiKeyScopes: [],
        authSource: null,
      });
    }
    await next();
  };
}

export function authHasScope(auth: AuthContext, scope: string): boolean {
  if (auth.authSource !== 'api_key') {
    return true;
  }

  const scopes = auth.apiKeyScopes ?? [];
  return scopes.includes(scope) || scopes.includes('admin:*');
}

export function requireApiKeyScope(...allowedScopes: string[]) {
  return async (c: Context, next: Next) => {
    const auth = c.get('auth') as AuthContext | undefined;

    if (!auth?.isAuthenticated || !auth.user) {
      return c.json({ error: 'Unauthorized' }, 401);
    }

    if (
      auth.authSource === 'api_key' &&
      !allowedScopes.some((scope) => authHasScope(auth, scope))
    ) {
      return c.json({ error: 'Insufficient API key scope' }, 403);
    }

    await next();
  };
}

export function requireTier(...tiers: Array<'free' | 'pro' | 'team' | 'enterprise'>) {
  return async (c: Context, next: Next) => {
    const auth = c.get('auth') as AuthContext | undefined;

    if (!auth?.isAuthenticated || !auth.user) {
      return c.json({ error: 'Unauthorized' }, 401);
    }

    if (!tiers.includes(auth.user.tier)) {
      return c.json({ error: 'Insufficient tier. Upgrade required.' }, 403);
    }

    await next();
  };
}
