// ============================================================
// AUTENTIQUE — Integração GraphQL (multipart upload)
// Docs: https://docs.autentique.com.br/api
// ============================================================
import {
  Injectable, Logger, BadGatewayException, NotFoundException,
  BadRequestException, Module, Controller, Get, Post, Param,
  Body, UseGuards, Request,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AuthGuard } from '@nestjs/passport';
import { InjectRepository, TypeOrmModule } from '@nestjs/typeorm';
import {
  Repository, Entity, PrimaryGeneratedColumn, Column,
  CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne, JoinColumn, DataSource,
} from 'typeorm';
import { IsString, IsEmail, IsOptional, IsArray, ValidateNested, IsIn, IsDateString } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ContractsModule, ContractsService } from '../contracts/contracts.module';

export interface AutentiqueSignerInput {
  name: string;
  email: string;
  action?: 'SIGN' | 'APPROVE' | 'WITNESS';
}

export interface AutentiqueDocumentResult {
  id: string;
  name: string;
  rejected_count?: number;
  files?: { original?: string; signed?: string };
  signatures?: Array<{
    public_id: string;
    name: string;
    email: string;
    action: { name: string };
    signed?: { created_at: string } | null;
    link: { short_link: string };
  }>;
}

// ============================================================
// AUTENTIQUE SERVICE
// ============================================================

@Injectable()
export class AutentiqueService {
  private readonly logger = new Logger(AutentiqueService.name);
  private readonly apiUrl: string;

  constructor(private readonly config: ConfigService) {
    this.apiUrl = config.get<string>('AUTENTIQUE_API_URL', 'https://api.autentique.com.br/v2/graphql');
  }

  // ----------------------------------------------------------
  // Criar documento via multipart/form-data (GraphQL Upload spec)
  // ----------------------------------------------------------
  async createDocument(
    token: string,
    params: {
      name: string;
      pdfBase64: string;
      signers: AutentiqueSignerInput[];
      deadline?: string;
    },
  ): Promise<AutentiqueDocumentResult> {
    const mutation = `
      mutation CreateDocumentMutation(
        $document: DocumentInput!
        $signers: [SignerInput!]!
        $file: Upload!
      ) {
        createDocument(document: $document, signers: $signers, file: $file) {
          id
          name
          rejected_count
          files { original signed }
          signatures {
            public_id
            name
            email
            action { name }
            signed { created_at }
            link { short_link }
          }
        }
      }
    `;

    const variables = {
      document: {
        name: params.name,
        ...(params.deadline ? { deadline_at: params.deadline } : {}),
      },
      signers: params.signers.map((s) => ({
        name: s.name,
        email: s.email,
        action: s.action || 'SIGN',
      })),
      file: null,
    };

    const buffer = Buffer.from(params.pdfBase64, 'base64');
    const blob = new Blob([buffer], { type: 'application/pdf' });

    const formData = new FormData();
    formData.append('operations', JSON.stringify({ query: mutation, variables }));
    formData.append('map', JSON.stringify({ '0': ['variables.file'] }));
    formData.append('0', blob, 'document.pdf');

    try {
      const response = await fetch(this.apiUrl, {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });

      const data = (await response.json()) as { data?: { createDocument: AutentiqueDocumentResult }; errors?: Array<{ message: string }> };

      if (data.errors?.length) {
        throw new BadGatewayException(`Autentique: ${data.errors[0].message}`);
      }

      return data.data!.createDocument;
    } catch (err: any) {
      if (err instanceof BadGatewayException) throw err;
      this.logger.error('Autentique createDocument error', err.message);
      throw new BadGatewayException('Falha ao enviar documento para Autentique');
    }
  }

  // ----------------------------------------------------------
  // Consultar status do documento
  // ----------------------------------------------------------
  async getDocument(token: string, documentId: string): Promise<AutentiqueDocumentResult> {
    const query = `
      query {
        document(id: "${documentId}") {
          id
          name
          rejected_count
          files { original signed }
          signatures {
            public_id
            name
            email
            action { name }
            signed { created_at }
            link { short_link }
          }
        }
      }
    `;

    try {
      const response = await fetch(this.apiUrl, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ query }),
      });

      const data = (await response.json()) as { data?: { document: AutentiqueDocumentResult }; errors?: Array<{ message: string }> };

      if (data.errors?.length) {
        throw new BadGatewayException(`Autentique: ${data.errors[0].message}`);
      }

      return data.data!.document;
    } catch (err: any) {
      if (err instanceof BadGatewayException) throw err;
      this.logger.error('Autentique getDocument error', err.message);
      throw new BadGatewayException('Falha ao consultar documento na Autentique');
    }
  }

  // ----------------------------------------------------------
  // Listar documentos
  // ----------------------------------------------------------
  async listDocuments(token: string, page = 1): Promise<AutentiqueDocumentResult[]> {
    const query = `
      query {
        documents(page: ${page}) {
          data {
            id
            name
            rejected_count
          }
        }
      }
    `;

    try {
      const response = await fetch(this.apiUrl, {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
        body: JSON.stringify({ query }),
      });
      const data = (await response.json()) as { data?: { documents: { data: AutentiqueDocumentResult[] } } };
      return data.data?.documents?.data ?? [];
    } catch (err: any) {
      this.logger.error('Autentique listDocuments error', err.message);
      throw new BadGatewayException('Falha ao listar documentos na Autentique');
    }
  }

  // ----------------------------------------------------------
  // Deletar documento
  // ----------------------------------------------------------
  async deleteDocument(token: string, documentId: string): Promise<boolean> {
    const mutation = `mutation { deleteDocument(id: "${documentId}") }`;
    try {
      const response = await fetch(this.apiUrl, {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
        body: JSON.stringify({ query: mutation }),
      });
      const data = (await response.json()) as { data?: { deleteDocument: boolean } };
      return data.data?.deleteDocument ?? false;
    } catch (err: any) {
      this.logger.error('Autentique deleteDocument error', err.message);
      throw new BadGatewayException('Falha ao deletar documento na Autentique');
    }
  }
}

// ============================================================
// ENTITIES
// ============================================================

@Entity('signatures')
export class Signature {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ name: 'tenant_id' }) tenantId: string;
  @Column({ name: 'contract_id' }) contractId: string;
  @Column({ name: 'autentique_doc_id' }) autentiqueDocId: string;
  @Column({ name: 'autentique_url', nullable: true }) autentiqueUrl: string;
  @Column({ name: 'deadline_at', type: 'timestamptz', nullable: true }) deadlineAt: Date;
  @Column({ default: 'pending' }) status: string;   // pending | signed | refused | expired
  @Column({ name: 'signed_at', type: 'timestamptz', nullable: true }) signedAt: Date;
  @OneToMany(() => SignatureSigner, (s) => s.signature, { cascade: true, eager: true })
  signers: SignatureSigner[];
  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date;
}

@Entity('signature_signers')
export class SignatureSigner {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ name: 'signature_id' }) signatureId: string;
  @Column({ name: 'tenant_id' }) tenantId: string;
  @Column({ length: 150 }) name: string;
  @Column({ length: 80 }) email: string;
  @Column({ default: 'sign' }) role: string;   // sign | approve | witness
  @Column({ name: 'autentique_action_id', nullable: true }) autentiqueActionId: string;
  @Column({ name: 'signed_at', type: 'timestamptz', nullable: true }) signedAt: Date;
  @Column({ default: 'pending' }) status: string;
  @ManyToOne(() => Signature, (s) => s.signers, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'signature_id' })
  signature: Signature;
  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
}

// ============================================================
// DTOs
// ============================================================

export class SignerDto {
  @ApiProperty({ example: 'João Silva' })
  @IsString() name: string;

  @ApiProperty({ example: 'joao@empresa.com' })
  @IsEmail() email: string;

  @ApiPropertyOptional({ enum: ['SIGN', 'APPROVE', 'WITNESS'], default: 'SIGN' })
  @IsOptional() @IsIn(['SIGN', 'APPROVE', 'WITNESS']) action?: 'SIGN' | 'APPROVE' | 'WITNESS';
}

export class CreateSignatureDto {
  @ApiProperty({ description: 'ID do contrato' })
  @IsString() contractId: string;

  @ApiProperty({ description: 'PDF do contrato em base64' })
  @IsString() pdfBase64: string;

  @ApiProperty({ description: 'Nome do documento na Autentique' })
  @IsString() documentName: string;

  @ApiPropertyOptional({ description: 'Prazo para assinatura (ISO date)' })
  @IsOptional() @IsDateString() deadline?: string;

  @ApiProperty({ type: [SignerDto] })
  @IsArray() @ValidateNested({ each: true }) @Type(() => SignerDto) signers: SignerDto[];
}

// ============================================================
// SERVICE: SignaturesService
// ============================================================

@Injectable()
export class SignaturesService {
  private readonly logger = new Logger(SignaturesService.name);

  constructor(
    @InjectRepository(Signature)
    private readonly signatureRepo: Repository<Signature>,

    @InjectRepository(SignatureSigner)
    private readonly signerRepo: Repository<SignatureSigner>,

    private readonly autentiqueService: AutentiqueService,
    private readonly contractsService: ContractsService,
    private readonly dataSource: DataSource,
  ) {}

  private async getTenantToken(tenantId: string): Promise<string> {
    const rows = await this.dataSource.query(
      'SELECT autentique_token FROM tenants WHERE id = $1',
      [tenantId],
    );
    const token = rows[0]?.autentique_token;
    if (!token) {
      throw new BadRequestException('Token da Autentique não configurado. Configure em Configurações > Integrações.');
    }
    return token;
  }

  async createAndSend(tenantId: string, dto: CreateSignatureDto): Promise<Signature> {
    const token = await this.getTenantToken(tenantId);

    const doc = await this.autentiqueService.createDocument(token, {
      name: dto.documentName,
      pdfBase64: dto.pdfBase64,
      signers: dto.signers,
      deadline: dto.deadline,
    });

    // Autentique não expõe mais um link único do documento — cada signatário
    // tem seu próprio link de assinatura; usamos o do primeiro como referência.
    const primaryLink = doc.signatures?.[0]?.link?.short_link;

    const signature = this.signatureRepo.create({
      tenantId,
      contractId: dto.contractId,
      autentiqueDocId: doc.id,
      autentiqueUrl: primaryLink,
      deadlineAt: dto.deadline ? new Date(dto.deadline) : null,
      status: 'pending',
      signers: doc.signatures?.map((s) =>
        this.signerRepo.create({
          tenantId,
          // Fallback pro nome enviado no request: a Autentique pode devolver
          // o signatário sem name preenchido, e a coluna local é NOT NULL.
          name: s.name || dto.signers.find((ds) => ds.email === s.email)?.name || s.email,
          email: s.email,
          role: s.action?.name?.toLowerCase() || 'sign',
          autentiqueActionId: s.public_id,
          status: s.signed?.created_at ? 'signed' : 'pending',
          signedAt: s.signed?.created_at ? new Date(s.signed.created_at) : null,
        }),
      ) ?? [],
    });

    const saved = await this.signatureRepo.save(signature);

    await this.contractsService.linkAutentique(tenantId, dto.contractId, doc.id, primaryLink);

    return saved;
  }

  async syncStatus(tenantId: string, signatureId: string): Promise<Signature> {
    const sig = await this.signatureRepo.findOne({
      where: { id: signatureId, tenantId },
      relations: ['signers'],
    });
    if (!sig) throw new NotFoundException('Assinatura não encontrada');

    const token = await this.getTenantToken(tenantId);
    const doc = await this.autentiqueService.getDocument(token, sig.autentiqueDocId);

    for (const remoteSigner of doc.signatures ?? []) {
      const local = sig.signers.find((ls) => ls.autentiqueActionId === remoteSigner.public_id);
      if (local && remoteSigner.signed?.created_at && !local.signedAt) {
        local.signedAt = new Date(remoteSigner.signed.created_at);
        local.status = 'signed';
        await this.signerRepo.save(local);
      }
    }

    const allSigned = (doc.signatures ?? []).length > 0 && (doc.signatures ?? []).every((s) => !!s.signed?.created_at);

    if (allSigned && sig.status !== 'signed') {
      sig.status = 'signed';
      sig.signedAt = new Date();
      try {
        await this.contractsService.markSigned(tenantId, sig.contractId);
      } catch (err: any) {
        this.logger.warn(`markSigned falhou para contrato ${sig.contractId}: ${err.message}`);
      }
    } else if ((doc.rejected_count ?? 0) > 0) {
      sig.status = 'refused';
    }

    return this.signatureRepo.save(sig);
  }

  // ----------------------------------------------------------
  // Cancelar envio para assinatura
  // ----------------------------------------------------------
  async cancel(tenantId: string, signatureId: string): Promise<Signature> {
    const sig = await this.signatureRepo.findOne({
      where: { id: signatureId, tenantId },
      relations: ['signers'],
    });
    if (!sig) throw new NotFoundException('Assinatura não encontrada');
    if (sig.status === 'signed') {
      throw new BadRequestException('Não é possível cancelar uma assinatura já concluída');
    }
    if (sig.status === 'cancelled') {
      throw new BadRequestException('Este envio já foi cancelado');
    }

    const token = await this.getTenantToken(tenantId);
    await this.autentiqueService.deleteDocument(token, sig.autentiqueDocId);

    sig.status = 'cancelled';
    const saved = await this.signatureRepo.save(sig);

    try {
      await this.contractsService.revertToDraft(tenantId, sig.contractId);
    } catch (err: any) {
      this.logger.warn(`revertToDraft falhou para contrato ${sig.contractId}: ${err.message}`);
    }

    return saved;
  }

  async findByContract(tenantId: string, contractId: string): Promise<Signature[]> {
    return this.signatureRepo.find({
      where: { tenantId, contractId },
      relations: ['signers'],
    });
  }

  async findByAutentiqueDocId(docId: string): Promise<Signature[]> {
    return this.signatureRepo.find({ where: { autentiqueDocId: docId } });
  }
}

// ============================================================
// CONTROLLER
// ============================================================

@ApiTags('Assinaturas')
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'))
@Controller('signatures')
export class SignaturesController {
  constructor(private readonly signaturesService: SignaturesService) {}

  @Post()
  @ApiOperation({ summary: 'Criar e enviar documento para assinatura via Autentique' })
  create(@Request() req: any, @Body() dto: CreateSignatureDto) {
    return this.signaturesService.createAndSend(req.user.tenantId, dto);
  }

  @Post(':id/sync')
  @ApiOperation({ summary: 'Sincronizar status de assinatura com Autentique' })
  sync(@Request() req: any, @Param('id') id: string) {
    return this.signaturesService.syncStatus(req.user.tenantId, id);
  }

  @Post(':id/cancel')
  @ApiOperation({ summary: 'Cancelar envio para assinatura (apaga o documento na Autentique)' })
  cancel(@Request() req: any, @Param('id') id: string) {
    return this.signaturesService.cancel(req.user.tenantId, id);
  }

  @Get('contract/:contractId')
  @ApiOperation({ summary: 'Listar assinaturas de um contrato' })
  byContract(@Request() req: any, @Param('contractId') contractId: string) {
    return this.signaturesService.findByContract(req.user.tenantId, contractId);
  }
}

@Controller('signatures/webhook')
export class SignaturesWebhookController {
  private readonly logger = new Logger(SignaturesWebhookController.name);

  constructor(private readonly signaturesService: SignaturesService) {}

  @Post('autentique')
  @ApiOperation({ summary: 'Webhook da Autentique — chamado automaticamente ao assinar' })
  async handleAutentiqueWebhook(@Body() payload: any) {
    const docId = payload?.document?.id as string | undefined;
    if (!docId) return { ok: false, reason: 'no document id' };

    const sigs = await this.signaturesService.findByAutentiqueDocId(docId);
    for (const sig of sigs) {
      try {
        await this.signaturesService.syncStatus(sig.tenantId, sig.id);
      } catch (err: any) {
        this.logger.warn(`webhook syncStatus falhou para signature ${sig.id}: ${err.message}`);
      }
    }

    return { ok: true, processed: sigs.length };
  }
}

// ============================================================
// MODULE
// ============================================================

@Module({
  imports: [
    TypeOrmModule.forFeature([Signature, SignatureSigner]),
    ContractsModule,
  ],
  controllers: [SignaturesController, SignaturesWebhookController],
  providers: [SignaturesService, AutentiqueService],
  exports: [SignaturesService, AutentiqueService],
})
export class SignaturesModule {}
