import * as jose from 'jose';
import * as argon2 from 'argon2';
import bcrypt from 'bcryptjs';
import crypto from 'crypto';
import { Context } from 'hono';
import { db, dbGet, dbAll, users, magicLinkTokens, userSessions } from '../db/index.js';
import { eq, and, gt } from 'drizzle-orm';
import { generateId, getNow, toDbDate } from './utils.js';

const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET || 'your-super-secret-key-change-in-production');
const JWT_ISSUER = 'spanglish';
const JWT_AUDIENCE = 'spanglish-app';

export interface JWTPayload {
  sub: string;
  email: string;
  role: string;
  iat: number;
  exp: number;
}

// Password hashing with Argon2 (spec requirement)
export async function hashPassword(password: string): Promise<string> {
  return argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 65536, // 64 MB
    timeCost: 3,
    parallelism: 4,
  });
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  // Support both bcrypt (legacy) and argon2 hashes for migration
  if (hash.startsWith('$argon2')) {
    return argon2.verify(hash, password);
  }
  // Legacy bcrypt support
  return bcrypt.compare(password, hash);
}

// Generate secure random token for magic links
export function generateSecureToken(): string {
  return crypto.randomBytes(32).toString('hex');
}

// Create magic link token
export async function createMagicLinkToken(
  userId: string,
  type: 'login' | 'reset_password' | 'claim_account' | 'email_verification',
  expiresInMinutes: number = 10
): Promise<string> {
  const token = generateSecureToken();
  const now = getNow();
  const expiresAt = toDbDate(new Date(Date.now() + expiresInMinutes * 60 * 1000));

  await (db as any).insert(magicLinkTokens).values({
    id: generateId(),
    userId,
    token,
    type,
    expiresAt,
    createdAt: now,
  });

  return token;
}

// Verify and consume magic link token
export async function verifyMagicLinkToken(
  token: string,
  type: 'login' | 'reset_password' | 'claim_account' | 'email_verification'
): Promise<{ valid: boolean; userId?: string; error?: string }> {
  const now = getNow();
  
  const tokenRecord = await dbGet<any>(
    (db as any)
      .select()
      .from(magicLinkTokens)
      .where(
        and(
          eq((magicLinkTokens as any).token, token),
          eq((magicLinkTokens as any).type, type)
        )
      )
  );

  if (!tokenRecord) {
    return { valid: false, error: 'Invalid token' };
  }

  if (tokenRecord.usedAt) {
    return { valid: false, error: 'Token already used' };
  }

  if (new Date(tokenRecord.expiresAt) < new Date()) {
    return { valid: false, error: 'Token expired' };
  }

  // Mark token as used
  await (db as any)
    .update(magicLinkTokens)
    .set({ usedAt: now })
    .where(eq((magicLinkTokens as any).id, tokenRecord.id));

  return { valid: true, userId: tokenRecord.userId };
}

// Create user session
export async function createUserSession(
  userId: string,
  userAgent?: string,
  ipAddress?: string
): Promise<string> {
  const sessionToken = generateSecureToken();
  const now = getNow();
  const expiresAt = toDbDate(new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)); // 30 days

  await (db as any).insert(userSessions).values({
    id: generateId(),
    userId,
    token: sessionToken,
    userAgent: userAgent || null,
    ipAddress: ipAddress || null,
    lastActiveAt: now,
    expiresAt,
    createdAt: now,
  });

  return sessionToken;
}

// Get user's active sessions
export async function getUserSessions(userId: string) {
  const now = getNow();
  
  return dbAll(
    (db as any)
      .select()
      .from(userSessions)
      .where(
        and(
          eq((userSessions as any).userId, userId),
          gt((userSessions as any).expiresAt, now)
        )
      )
  );
}

// Invalidate a specific session
export async function invalidateSession(sessionId: string, userId: string): Promise<boolean> {
  const result = await (db as any)
    .delete(userSessions)
    .where(
      and(
        eq((userSessions as any).id, sessionId),
        eq((userSessions as any).userId, userId)
      )
    );
  
  return true;
}

// Invalidate all user sessions (logout everywhere)
export async function invalidateAllUserSessions(userId: string): Promise<void> {
  await (db as any)
    .delete(userSessions)
    .where(eq((userSessions as any).userId, userId));
}

// Password validation (min 10 characters per spec)
export function validatePassword(password: string): { valid: boolean; error?: string } {
  if (password.length < 10) {
    return { valid: false, error: 'Password must be at least 10 characters long' };
  }
  return { valid: true };
}

export async function createToken(userId: string, email: string, role: string): Promise<string> {
  const token = await new jose.SignJWT({ sub: userId, email, role })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setIssuer(JWT_ISSUER)
    .setAudience(JWT_AUDIENCE)
    .setExpirationTime('7d')
    .sign(JWT_SECRET);
  
  return token;
}

export async function createRefreshToken(userId: string): Promise<string> {
  const token = await new jose.SignJWT({ sub: userId, type: 'refresh' })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setIssuer(JWT_ISSUER)
    .setExpirationTime('30d')
    .sign(JWT_SECRET);
  
  return token;
}

export async function verifyToken(token: string): Promise<JWTPayload | null> {
  try {
    const { payload } = await jose.jwtVerify(token, JWT_SECRET, {
      issuer: JWT_ISSUER,
      audience: JWT_AUDIENCE,
    });
    return payload as unknown as JWTPayload;
  } catch {
    return null;
  }
}

export async function getAuthUser(c: Context): Promise<any | null> {
  const authHeader = c.req.header('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return null;
  }
  
  const token = authHeader.slice(7);
  const payload = await verifyToken(token);
  
  if (!payload) {
    return null;
  }
  
  const user = await dbGet<any>(
    (db as any).select().from(users).where(eq((users as any).id, payload.sub))
  );
  return user || null;
}

export function requireAuth(roles?: string[]) {
  return async (c: Context, next: () => Promise<void>) => {
    const user = await getAuthUser(c);
    
    if (!user) {
      return c.json({ error: 'Unauthorized' }, 401);
    }
    
    if (roles && !roles.includes(user.role)) {
      return c.json({ error: 'Forbidden' }, 403);
    }
    
    c.set('user', user);
    await next();
  };
}

export async function isFirstUser(): Promise<boolean> {
  const result = await dbAll(
    (db as any).select().from(users).limit(1)
  );
  return !result || result.length === 0;
}
