// ============================================================
// ADMIN MODULE — Painel do administrador Klavo
// Provisionamento de tenants e usuários
// Acesso protegido por KLAVO_ADMIN_SECRET no env
// ============================================================
import {
  Module, Injectable, UnauthorizedException, NotFoundException, ForbiddenException,
  CanActivate, ExecutionContext, Controller, Post, Get, Delete,
  Body, Param, Request, UseGuards, HttpCode, HttpStatus, Logger,
} from '@nestjs/common';
import { InjectRepository, TypeOrmModule } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiProperty } from '@nestjs/swagger';
import { IsString, IsEmail, MinLength } from 'class-validator';
import * as bcrypt from 'bcrypt';

import { User } from '../auth/auth.module';
import { Tenant } from '../auth/auth.module';
import { UserTenantMembership } from '../auth/auth.module';
import { AuthModule } from '../auth/auth.module';

// ============================================================
// DTOs
// ============================================================
export class AdminLoginDto {
  @ApiProperty({ description: 'Chave secreta do administrador Klavo (KLAVO_ADMIN_SECRET)' })
  @IsString()
  secret: string;
}

export class ProvisionTenantDto {
  @ApiProperty({ example: 'Empresa XYZ Ltda' }) @IsString() @MinLength(2) tenantName: string;
  @ApiProperty({ example: '12345678000195', description: 'CNPJ sem pontuação' }) @IsString() @MinLength(14) tenantDocument: string;
  @ApiProperty() @IsEmail() tenantEmail: string;
  @ApiProperty({ example: 'João Silva' }) @IsString() @MinLength(2) userName: string;
  @ApiProperty() @IsEmail() userEmail: string;
  @ApiProperty({ example: 'senha123', minLength: 6 }) @IsString() @MinLength(6) userPassword: string;
}

// ============================================================
// GUARD — verifica role klavo_admin no JWT
// ============================================================
@Injectable()
export class AdminGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToHttp().getRequest();
    const user = req.user;
    if (!user || user.role !== 'klavo_admin') {
      throw new UnauthorizedException('Acesso restrito ao administrador Klavo');
    }
    return true;
  }
}

// ============================================================
// SERVICE
// ============================================================
@Injectable()
export class AdminService {
  private readonly logger = new Logger(AdminService.name);

  constructor(
    @InjectRepository(User) private readonly userRepo: Repository<User>,
    @InjectRepository(Tenant) private readonly tenantRepo: Repository<Tenant>,
    @InjectRepository(UserTenantMembership) private readonly membershipRepo: Repository<UserTenantMembership>,
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
    private readonly dataSource: DataSource,
  ) {}

  login(secret: string): { access_token: string } {
    const adminSecret = this.configService.get<string>('KLAVO_ADMIN_SECRET');
    if (!adminSecret || secret !== adminSecret) {
      throw new UnauthorizedException('Segredo inválido');
    }
    const token = this.jwtService.sign(
      { sub: 'klavo_admin', role: 'klavo_admin' },
      { expiresIn: '4h' },
    );
    return { access_token: token };
  }

  async listTenants(): Promise<Array<Tenant & { userCount: number; ownerEmail: string }>> {
    const tenants = await this.tenantRepo.find({ order: { createdAt: 'DESC' } });

    const result = await Promise.all(
      tenants.map(async (t) => {
        const [userCount] = await this.dataSource.query(
          'SELECT COUNT(*)::int AS cnt FROM users WHERE tenant_id = $1',
          [t.id],
        );
        const owner = await this.membershipRepo.findOne({
          where: { tenantId: t.id, role: 'owner', status: 'active' },
          order: { createdAt: 'ASC' },
        });
        // Omit sensitive fields
        const { nfseCertificate, nfseCertificatePass, autentiqueToken, proposalTemplate, ...safe } = t as any;
        return { ...safe, userCount: userCount?.cnt ?? 0, ownerEmail: owner?.email ?? '' };
      }),
    );

    return result;
  }

  async provision(dto: ProvisionTenantDto): Promise<{ tenant: Tenant; userEmail: string }> {
    const exists = await this.tenantRepo.findOneBy({ document: dto.tenantDocument });
    if (exists) throw new UnauthorizedException('CNPJ já cadastrado');

    const emailExists = await this.userRepo.findOneBy({ email: dto.userEmail });
    if (emailExists) throw new UnauthorizedException('E-mail de usuário já em uso');

    const tenant = await this.tenantRepo.save(
      this.tenantRepo.create({
        name: dto.tenantName,
        document: dto.tenantDocument,
        email: dto.tenantEmail,
        plan: 'trial',
        status: 'active',
      }),
    );

    const passwordHash = await bcrypt.hash(dto.userPassword, 10);
    const user = await this.userRepo.save(
      this.userRepo.create({
        tenantId: tenant.id,
        name: dto.userName,
        email: dto.userEmail,
        passwordHash,
        isOwner: true,
        status: 'active',
      }),
    );

    await this.membershipRepo.save(
      this.membershipRepo.create({ email: user.email, tenantId: tenant.id, role: 'owner' }),
    );

    this.logger.log(`Tenant provisionado: ${tenant.name} (${tenant.id}) — owner: ${user.email}`);
    return { tenant, userEmail: user.email };
  }

  // ----------------------------------------------------------
  // Métodos para uso via JWT normal (plan === 'klavo')
  // ----------------------------------------------------------
  private async assertKlavoTenant(tenantId: string): Promise<void> {
    const t = await this.tenantRepo.findOneBy({ id: tenantId });
    if (!t || t.plan !== 'klavo') {
      throw new ForbiddenException('Acesso restrito à equipe Klavo');
    }
  }

  async listClients(requestingTenantId: string) {
    await this.assertKlavoTenant(requestingTenantId);
    return this.listTenants();
  }

  async provisionClient(requestingTenantId: string, dto: ProvisionTenantDto) {
    await this.assertKlavoTenant(requestingTenantId);
    return this.provision(dto);
  }

  async deleteClient(requestingTenantId: string, id: string): Promise<void> {
    await this.assertKlavoTenant(requestingTenantId);
    if (id === requestingTenantId) throw new ForbiddenException('Não é possível excluir o tenant da plataforma');
    return this.deleteTenant(id);
  }

  async runMigrations(): Promise<{ applied: string[]; errors: string[] }> {
    const statements: { name: string; sql: string }[] = [
      { name: 'tenant_sequences table', sql: `CREATE TABLE IF NOT EXISTS tenant_sequences (
          tenant_id  UUID        NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
          entity     VARCHAR(20) NOT NULL,
          last_value BIGINT      NOT NULL DEFAULT 0,
          PRIMARY KEY (tenant_id, entity)
        )` },
      { name: 'invoices table', sql: `CREATE TABLE IF NOT EXISTS invoices (
          id                UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
          tenant_id         UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
          charge_id         UUID REFERENCES charges(id),
          contract_id       UUID REFERENCES contracts(id),
          client_id         UUID NOT NULL REFERENCES clients(id),
          rps_number        BIGINT        NOT NULL,
          rps_serie         VARCHAR(5)    NOT NULL DEFAULT 'RPS',
          rps_type          SMALLINT      DEFAULT 1,
          rps_status        SMALLINT      DEFAULT 1,
          competence_date   DATE          NOT NULL,
          nfse_number       VARCHAR(15),
          nfse_verify_code  VARCHAR(9),
          nfse_url          TEXT,
          nfse_xml          TEXT,
          nfse_issued_at    TIMESTAMPTZ,
          service_amount    NUMERIC(15,2) NOT NULL,
          deductions        NUMERIC(15,2) DEFAULT 0,
          pis               NUMERIC(15,2) DEFAULT 0,
          cofins            NUMERIC(15,2) DEFAULT 0,
          inss              NUMERIC(15,2) DEFAULT 0,
          ir                NUMERIC(15,2) DEFAULT 0,
          csll              NUMERIC(15,2) DEFAULT 0,
          iss_amount        NUMERIC(15,2),
          iss_retained      BOOLEAN       DEFAULT FALSE,
          iss_aliquota      NUMERIC(4,2),
          net_amount        NUMERIC(15,2) NOT NULL,
          service_list_item VARCHAR(5)    NOT NULL,
          cnae_code         VARCHAR(7),
          discrimination    TEXT          NOT NULL,
          service_city_code VARCHAR(7)    NOT NULL,
          iss_exigibility   SMALLINT      DEFAULT 1,
          cancel_code       SMALLINT,
          cancel_reason     TEXT,
          cancelled_at      TIMESTAMPTZ,
          cancel_xml        TEXT,
          email_sent_at     TIMESTAMPTZ,
          status            VARCHAR(20)   DEFAULT 'pending',
          error_message     TEXT,
          retry_count       SMALLINT      DEFAULT 0,
          abrasf_protocol   VARCHAR(50),
          created_at        TIMESTAMPTZ   DEFAULT NOW(),
          updated_at        TIMESTAMPTZ   DEFAULT NOW()
        )` },
      { name: 'invoices index tenant',  sql: `CREATE INDEX IF NOT EXISTS idx_invoices_tenant ON invoices(tenant_id)` },
      { name: 'invoices index status',  sql: `CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status)` },
      { name: 'tenants.proposal_template', sql: `ALTER TABLE tenants ADD COLUMN IF NOT EXISTS proposal_template TEXT` },
      { name: 'tenants.logo_url',          sql: `ALTER TABLE tenants ADD COLUMN IF NOT EXISTS logo_url TEXT` },
      { name: 'clients.phone_commercial',  sql: `ALTER TABLE clients ADD COLUMN IF NOT EXISTS phone_commercial VARCHAR(20)` },
      { name: 'clients.phone_financial',   sql: `ALTER TABLE clients ADD COLUMN IF NOT EXISTS phone_financial  VARCHAR(20)` },
      { name: 'clients.email_commercial',  sql: `ALTER TABLE clients ADD COLUMN IF NOT EXISTS email_commercial VARCHAR(80)` },
      { name: 'clients.email_financial',   sql: `ALTER TABLE clients ADD COLUMN IF NOT EXISTS email_financial  VARCHAR(80)` },
      { name: 'clients.email_nf',          sql: `ALTER TABLE clients ADD COLUMN IF NOT EXISTS email_nf         VARCHAR(80)` },
      { name: 'clients.name VARCHAR(255)',  sql: `ALTER TABLE clients ALTER COLUMN name TYPE VARCHAR(255)` },
      { name: 'clients.address VARCHAR(255)', sql: `ALTER TABLE clients ALTER COLUMN address TYPE VARCHAR(255)` },
      { name: 'clients.neighborhood VARCHAR(120)', sql: `ALTER TABLE clients ALTER COLUMN neighborhood TYPE VARCHAR(120)` },
      { name: 'clients.document VARCHAR(18)', sql: `ALTER TABLE clients ALTER COLUMN document TYPE VARCHAR(18)` },
      { name: 'invoices.email_sent_at',    sql: `ALTER TABLE invoices ADD COLUMN IF NOT EXISTS email_sent_at TIMESTAMPTZ` },
      { name: 'user_tenant_memberships',   sql: `CREATE TABLE IF NOT EXISTS user_tenant_memberships (
          id         UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
          email      VARCHAR(80) NOT NULL,
          tenant_id  UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
          role       VARCHAR(20) NOT NULL DEFAULT 'owner',
          status     VARCHAR(20) NOT NULL DEFAULT 'active',
          created_at TIMESTAMPTZ DEFAULT NOW(),
          UNIQUE(email, tenant_id)
        )` },
      { name: 'user_tenant_memberships index', sql: `CREATE INDEX IF NOT EXISTS idx_utm_email ON user_tenant_memberships(email)` },
      { name: 'backfill memberships', sql: `INSERT INTO user_tenant_memberships (email, tenant_id, role, status)
          SELECT u.email, u.tenant_id, CASE WHEN u.is_owner THEN 'owner' ELSE 'member' END, u.status
          FROM users u ON CONFLICT (email, tenant_id) DO NOTHING` },
      { name: 'proposals.installments_count', sql: `ALTER TABLE proposals ADD COLUMN IF NOT EXISTS installments_count SMALLINT` },
      { name: 'proposals.installment_amount', sql: `ALTER TABLE proposals ADD COLUMN IF NOT EXISTS installment_amount NUMERIC(15,2)` },
      { name: 'proposals.first_due_date',     sql: `ALTER TABLE proposals ADD COLUMN IF NOT EXISTS first_due_date DATE` },
      { name: 'contracts.installments_count', sql: `ALTER TABLE contracts ADD COLUMN IF NOT EXISTS installments_count SMALLINT` },
      { name: 'contracts.installment_amount', sql: `ALTER TABLE contracts ADD COLUMN IF NOT EXISTS installment_amount NUMERIC(15,2)` },
      { name: 'contracts.first_due_date',     sql: `ALTER TABLE contracts ADD COLUMN IF NOT EXISTS first_due_date DATE` },
    ];

    const applied: string[] = [];
    const errors: string[] = [];

    for (const { name, sql } of statements) {
      try {
        await this.dataSource.query(sql);
        applied.push(name);
        this.logger.log(`Migration OK: ${name}`);
      } catch (err: any) {
        const msg = err?.message ?? String(err);
        errors.push(`${name}: ${msg}`);
        this.logger.error(`Migration FAILED: ${name}`, msg);
      }
    }

    return { applied, errors };
  }

  async deleteTenant(id: string): Promise<void> {
    const tenant = await this.tenantRepo.findOneBy({ id });
    if (!tenant) throw new NotFoundException('Empresa não encontrada');

    const tables = [
      'invoices', 'saas_usage_events', 'saas_usage_summaries',
      'saas_api_keys', 'saas_endpoints', 'saas_licenses',
      'charges', 'contracts', 'proposals', 'products', 'clients',
      'user_tenant_memberships', 'users',
    ];
    for (const table of tables) {
      await this.dataSource.query(`DELETE FROM "${table}" WHERE tenant_id = $1`, [id]);
    }
    await this.dataSource.query('DELETE FROM tenants WHERE id = $1', [id]);

    this.logger.warn(`Tenant deletado: ${tenant.name} (${id})`);
  }
}

// ============================================================
// CONTROLLER
// ============================================================
@ApiTags('Admin Klavo')
@Controller('admin')
export class AdminController {
  constructor(private readonly adminService: AdminService) {}

  @Post('login')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Login do administrador Klavo com chave secreta' })
  login(@Body() dto: AdminLoginDto) {
    return this.adminService.login(dto.secret);
  }

  @Get('tenants')
  @UseGuards(AuthGuard('jwt'), AdminGuard)
  @ApiBearerAuth()
  @ApiOperation({ summary: 'Listar todas as empresas cadastradas' })
  listTenants() {
    return this.adminService.listTenants();
  }

  @Post('tenants')
  @UseGuards(AuthGuard('jwt'), AdminGuard)
  @ApiBearerAuth()
  @ApiOperation({ summary: 'Provisionar nova empresa + usuário owner' })
  provision(@Body() dto: ProvisionTenantDto) {
    return this.adminService.provision(dto);
  }

  @Delete('tenants/:id')
  @UseGuards(AuthGuard('jwt'), AdminGuard)
  @ApiBearerAuth()
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({ summary: 'Deletar empresa e todos os dados (irreversível)' })
  deleteTenant(@Param('id') id: string) {
    return this.adminService.deleteTenant(id);
  }

  @Post('run-migrations')
  @UseGuards(AuthGuard('jwt'), AdminGuard)
  @ApiBearerAuth()
  @ApiOperation({ summary: 'Rodar migrations pendentes no banco de produção' })
  runMigrations() {
    return this.adminService.runMigrations();
  }

  // ----------------------------------------------------------
  // Rotas via JWT normal — só acessíveis ao tenant plan='klavo'
  // ----------------------------------------------------------
  @Get('clients')
  @UseGuards(AuthGuard('jwt'))
  @ApiBearerAuth()
  @ApiOperation({ summary: 'Listar empresas clientes (uso interno Klavo)' })
  listClients(@Request() req: any) {
    return this.adminService.listClients(req.user.tenantId);
  }

  @Post('clients')
  @UseGuards(AuthGuard('jwt'))
  @ApiBearerAuth()
  @ApiOperation({ summary: 'Provisionar nova empresa cliente (uso interno Klavo)' })
  provisionClient(@Request() req: any, @Body() dto: ProvisionTenantDto) {
    return this.adminService.provisionClient(req.user.tenantId, dto);
  }

  @Delete('clients/:id')
  @UseGuards(AuthGuard('jwt'))
  @ApiBearerAuth()
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({ summary: 'Excluir empresa cliente (uso interno Klavo)' })
  deleteClient(@Request() req: any, @Param('id') id: string) {
    return this.adminService.deleteClient(req.user.tenantId, id);
  }
}

// ============================================================
// MODULE
// ============================================================
@Module({
  imports: [
    TypeOrmModule.forFeature([User, Tenant, UserTenantMembership]),
    AuthModule,
  ],
  controllers: [AdminController],
  providers: [AdminService, AdminGuard],
})
export class AdminModule {}
