import { createHash, randomBytes } from 'node:crypto';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { and, eq, gt, lt } from 'drizzle-orm';
import type { AuthenticatedUser } from '../../common/auth/authenticated-user';
import { DatabaseService } from '../../common/database/database.service';
import { sessions, users } from '../../database/schema';

export interface SessionContext {
  ipAddress?: string;
  userAgent?: string;
}

export interface CreatedSession {
  token: string;
  expiresAt: Date;
}

@Injectable()
export class AuthSessionsService {
  constructor(
    private readonly database: DatabaseService,
    private readonly config: ConfigService,
  ) {}

  async create(userId: string, context: SessionContext): Promise<CreatedSession> {
    const token = randomBytes(32).toString('base64url');
    const expiresAt = new Date();
    expiresAt.setUTCDate(expiresAt.getUTCDate() + this.config.get<number>('SESSION_TTL_DAYS', 30));

    await this.database.db.insert(sessions).values({
      userId,
      tokenHash: this.hashToken(token),
      expiresAt,
      ipAddress: context.ipAddress?.slice(0, 64),
      userAgent: context.userAgent?.slice(0, 2000),
    });

    return { token, expiresAt };
  }

  async resolve(token: string): Promise<AuthenticatedUser | null> {
    const now = new Date();
    const [row] = await this.database.db
      .select({
        sessionId: sessions.id,
        lastSeenAt: sessions.lastSeenAt,
        userId: users.id,
        email: users.email,
        displayName: users.displayName,
        role: users.role,
      })
      .from(sessions)
      .innerJoin(users, eq(users.id, sessions.userId))
      .where(
        and(
          eq(sessions.tokenHash, this.hashToken(token)),
          gt(sessions.expiresAt, now),
          eq(users.isActive, true),
        ),
      )
      .limit(1);

    if (!row) return null;

    const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
    if (row.lastSeenAt < oneHourAgo) {
      await this.database.db
        .update(sessions)
        .set({ lastSeenAt: now })
        .where(eq(sessions.id, row.sessionId));
    }

    return {
      id: row.userId,
      email: row.email,
      displayName: row.displayName,
      role: row.role,
      sessionId: row.sessionId,
    };
  }

  async revoke(sessionId: string): Promise<void> {
    await this.database.db.delete(sessions).where(eq(sessions.id, sessionId));
  }

  async revokeAllForUser(userId: string): Promise<void> {
    await this.database.db.delete(sessions).where(eq(sessions.userId, userId));
  }

  async deleteExpired(): Promise<number> {
    const deleted = await this.database.db
      .delete(sessions)
      .where(lt(sessions.expiresAt, new Date()))
      .returning({ id: sessions.id });
    return deleted.length;
  }

  private hashToken(token: string): string {
    return createHash('sha256').update(token).digest('hex');
  }
}
