import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Inject,
  Param,
  Post,
  UseGuards,
} from '@nestjs/common';
import { randomBytes, createHash } from 'node:crypto';
import { eq, type Db, adminTokens } from '@mosaicstack/db';
import { v4 as uuid } from 'uuid';
import { DB } from '../database/database.module.js';
import { AdminGuard } from './admin.guard.js';
import { CurrentUser } from '../auth/current-user.decorator.js';
import type {
  CreateTokenDto,
  TokenCreatedDto,
  TokenDto,
  TokenListDto,
} from './admin-tokens.dto.js';

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

function toTokenDto(row: typeof adminTokens.$inferSelect): TokenDto {
  return {
    id: row.id,
    label: row.label,
    scope: row.scope,
    expiresAt: row.expiresAt?.toISOString() ?? null,
    lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
    createdAt: row.createdAt.toISOString(),
  };
}

@Controller('api/admin/tokens')
@UseGuards(AdminGuard)
export class AdminTokensController {
  constructor(@Inject(DB) private readonly db: Db) {}

  @Post()
  async create(
    @Body() dto: CreateTokenDto,
    @CurrentUser() user: { id: string },
  ): Promise<TokenCreatedDto> {
    const plaintext = randomBytes(32).toString('hex');
    const tokenHash = hashToken(plaintext);
    const id = uuid();

    const expiresAt = dto.expiresInDays
      ? new Date(Date.now() + dto.expiresInDays * 24 * 60 * 60 * 1000)
      : null;

    const [row] = await this.db
      .insert(adminTokens)
      .values({
        id,
        userId: user.id,
        tokenHash,
        label: dto.label ?? 'CLI token',
        scope: dto.scope ?? 'admin',
        expiresAt,
      })
      .returning();

    return { ...toTokenDto(row!), plaintext };
  }

  @Get()
  async list(@CurrentUser() user: { id: string }): Promise<TokenListDto> {
    const rows = await this.db
      .select()
      .from(adminTokens)
      .where(eq(adminTokens.userId, user.id))
      .orderBy(adminTokens.createdAt);

    return { tokens: rows.map(toTokenDto), total: rows.length };
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async revoke(@Param('id') id: string, @CurrentUser() _user: { id: string }): Promise<void> {
    await this.db.delete(adminTokens).where(eq(adminTokens.id, id));
  }
}
