import {
  Controller,
  Get,
  Post,
  Delete,
  Patch,
  Param,
  Body,
  UseGuards,
  Request,
  NotFoundException,
  Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { RolesGuard, Roles } from '@it0/common';
import { TenantProvisioningService } from '@it0/database';
import { Tenant } from '../../../domain/entities/tenant.entity';
import { AuthService } from '../../../application/services/auth.service';
import * as crypto from 'crypto';

@Controller('api/v1/admin/tenants')
@UseGuards(RolesGuard)
@Roles('admin')
export class TenantController {
  private readonly logger = new Logger(TenantController.name);

  constructor(
    @InjectRepository(Tenant)
    private readonly tenantRepository: Repository<Tenant>,
    private readonly tenantProvisioningService: TenantProvisioningService,
    private readonly authService: AuthService,
  ) {}

  private toDto(t: Tenant) {
    return {
      id: t.id,
      name: t.name,
      slug: t.slug,
      plan: t.plan,
      status: t.status,
      adminEmail: t.adminEmail,
      userCount: 0,
      quota: {
        maxServers: t.maxServers,
        maxUsers: t.maxUsers,
        maxStandingOrders: t.maxStandingOrders,
        maxAgentTokensPerMonth: t.maxAgentTokensPerMonth,
      },
      createdAt: t.createdAt,
      updatedAt: t.updatedAt,
    };
  }

  /**
   * GET /api/v1/admin/tenants
   * Returns all tenants from the public schema's tenants table.
   */
  @Get()
  async listTenants() {
    const tenants = await this.tenantRepository.find({ order: { createdAt: 'DESC' } });
    return tenants.map((t) => this.toDto(t));
  }

  /**
   * GET /api/v1/admin/tenants/:id
   * Returns a single tenant by ID.
   */
  @Get(':id')
  async getTenant(@Param('id') id: string) {
    const tenant = await this.tenantRepository.findOne({ where: { id } });
    if (!tenant) {
      throw new NotFoundException(`Tenant with id "${id}" not found`);
    }
    return this.toDto(tenant);
  }

  /**
   * POST /api/v1/admin/tenants
   * Creates a new tenant and provisions its database schema.
   */
  @Post()
  async createTenant(
    @Body()
    body: {
      name: string;
      slug: string;
      plan?: 'free' | 'pro' | 'enterprise';
      adminEmail: string;
      maxServers?: number;
      maxUsers?: number;
      maxStandingOrders?: number;
      maxAgentTokensPerMonth?: number;
    },
  ) {
    const tenant = this.tenantRepository.create({
      id: crypto.randomUUID(),
      name: body.name,
      slug: body.slug,
      plan: body.plan ?? 'free',
      status: 'active',
      adminEmail: body.adminEmail,
      maxServers: body.maxServers ?? 10,
      maxUsers: body.maxUsers ?? 5,
      maxStandingOrders: body.maxStandingOrders ?? 20,
      maxAgentTokensPerMonth: body.maxAgentTokensPerMonth ?? 100000,
    });

    const savedTenant = await this.tenantRepository.save(tenant);

    // Provision the tenant's database schema
    try {
      await this.tenantProvisioningService.provisionTenant(savedTenant.slug);
      this.logger.log(`Schema provisioned for tenant "${savedTenant.slug}"`);
    } catch (err) {
      this.logger.error(
        `Failed to provision schema for tenant "${savedTenant.slug}":`,
        err,
      );
      // The tenant record is already saved; schema provisioning can be retried
    }

    return this.toDto(savedTenant);
  }

  /**
   * PATCH /api/v1/admin/tenants/:id
   * Updates tenant metadata.
   */
  @Patch(':id')
  async updateTenant(@Param('id') id: string, @Body() body: any) {
    const tenant = await this.tenantRepository.findOne({ where: { id } });
    if (!tenant) {
      throw new NotFoundException(`Tenant with id "${id}" not found`);
    }

    if (body.name !== undefined) tenant.name = body.name;
    if (body.plan !== undefined) tenant.plan = body.plan;
    if (body.status !== undefined) tenant.status = body.status;
    if (body.adminEmail !== undefined) tenant.adminEmail = body.adminEmail;

    // Support nested quota object from frontend
    if (body.quota) {
      if (body.quota.maxServers !== undefined) tenant.maxServers = body.quota.maxServers;
      if (body.quota.maxUsers !== undefined) tenant.maxUsers = body.quota.maxUsers;
      if (body.quota.maxStandingOrders !== undefined) tenant.maxStandingOrders = body.quota.maxStandingOrders;
      if (body.quota.maxAgentTokensPerMonth !== undefined) tenant.maxAgentTokensPerMonth = body.quota.maxAgentTokensPerMonth;
    }

    // Also support flat fields
    if (body.maxServers !== undefined) tenant.maxServers = body.maxServers;
    if (body.maxUsers !== undefined) tenant.maxUsers = body.maxUsers;
    if (body.maxStandingOrders !== undefined) tenant.maxStandingOrders = body.maxStandingOrders;
    if (body.maxAgentTokensPerMonth !== undefined) tenant.maxAgentTokensPerMonth = body.maxAgentTokensPerMonth;

    const saved = await this.tenantRepository.save(tenant);
    return this.toDto(saved);
  }

  /* ---- Invitation Management ---- */

  /**
   * POST /api/v1/admin/tenants/:id/invites
   * Send an invitation to join this tenant.
   */
  @Post(':id/invites')
  async createInvite(
    @Param('id') tenantId: string,
    @Body() body: { email: string; role?: string },
    @Request() req: any,
  ) {
    const invite = await this.authService.createInvite(
      tenantId,
      body.email,
      body.role || 'viewer',
      req.user?.sub || 'system',
    );
    return {
      id: invite.id,
      email: invite.email,
      role: invite.role,
      token: invite.token,
      status: invite.status,
      expiresAt: invite.expiresAt,
      createdAt: invite.createdAt,
    };
  }

  /**
   * GET /api/v1/admin/tenants/:id/invites
   * List all invitations for this tenant.
   */
  @Get(':id/invites')
  async listInvites(@Param('id') tenantId: string) {
    const invites = await this.authService.listInvites(tenantId);
    return invites.map((inv) => ({
      id: inv.id,
      email: inv.email,
      role: inv.role,
      status: inv.status,
      expiresAt: inv.expiresAt,
      invitedBy: inv.invitedBy,
      createdAt: inv.createdAt,
    }));
  }

  /**
   * DELETE /api/v1/admin/tenants/:id/invites/:inviteId
   * Revoke a pending invitation.
   */
  @Delete(':id/invites/:inviteId')
  async revokeInvite(
    @Param('id') tenantId: string,
    @Param('inviteId') inviteId: string,
  ) {
    await this.authService.revokeInvite(inviteId, tenantId);
    return { success: true };
  }
}
