/**
 * Digital Signature Service
 * ERP Construccion - Modulo Documents (MAE-016)
 *
 * Logica de negocio para firmas digitales de documentos.
 * @gap GAP-006
 */

import { Repository, DataSource, LessThan } from 'typeorm';
import { DigitalSignature, SignatureType, SignatureStatus } from '../entities/digital-signature.entity';
import * as crypto from 'crypto';

// DTOs
export interface CreateSignatureRequestDto {
  documentId: string;
  versionId?: string;
  signerId: string;
  signerName: string;
  signerEmail?: string;
  signerRole?: string;
  signatureType?: SignatureType;
  expiresAt?: Date;
  metadata?: Record<string, any>;
}

export interface SignDocumentDto {
  signatureData?: string;
  certificateInfo?: Record<string, any>;
  certificateSerial?: string;
  certificateIssuer?: string;
  ipAddress?: string;
  userAgent?: string;
  geolocation?: Record<string, any>;
}

export interface RejectSignatureDto {
  reason: string;
}

export interface SignatureFilters {
  documentId?: string;
  signerId?: string;
  status?: SignatureStatus;
  signatureType?: SignatureType;
  fromDate?: Date;
  toDate?: Date;
}

interface PaginationOptions {
  page: number;
  limit: number;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export class DigitalSignatureService {
  private repository: Repository<DigitalSignature>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(DigitalSignature);
  }

  // ============================================
  // CRUD
  // ============================================

  async requestSignature(
    tenantId: string,
    dto: CreateSignatureRequestDto,
    _userId?: string
  ): Promise<DigitalSignature> {
    const signature = this.repository.create({
      tenantId,
      documentId: dto.documentId,
      versionId: dto.versionId,
      signerId: dto.signerId,
      signerName: dto.signerName,
      signerEmail: dto.signerEmail,
      signerRole: dto.signerRole,
      signatureType: dto.signatureType || 'simple',
      status: 'pending',
      expiresAt: dto.expiresAt,
      metadata: dto.metadata || {},
      requestedAt: new Date(),
    });

    return this.repository.save(signature);
  }

  async findById(tenantId: string, id: string): Promise<DigitalSignature | null> {
    return this.repository.findOne({
      where: { id, tenantId },
    });
  }

  async findAll(
    tenantId: string,
    filters: SignatureFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<DigitalSignature>> {
    const queryBuilder = this.repository.createQueryBuilder('sig')
      .where('sig.tenant_id = :tenantId', { tenantId });

    if (filters.documentId) {
      queryBuilder.andWhere('sig.document_id = :documentId', { documentId: filters.documentId });
    }
    if (filters.signerId) {
      queryBuilder.andWhere('sig.signer_id = :signerId', { signerId: filters.signerId });
    }
    if (filters.status) {
      queryBuilder.andWhere('sig.status = :status', { status: filters.status });
    }
    if (filters.signatureType) {
      queryBuilder.andWhere('sig.signature_type = :type', { type: filters.signatureType });
    }
    if (filters.fromDate) {
      queryBuilder.andWhere('sig.requested_at >= :fromDate', { fromDate: filters.fromDate });
    }
    if (filters.toDate) {
      queryBuilder.andWhere('sig.requested_at <= :toDate', { toDate: filters.toDate });
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .orderBy('sig.requested_at', 'DESC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  // ============================================
  // OPERACIONES DE FIRMA
  // ============================================

  async signDocument(
    tenantId: string,
    id: string,
    dto: SignDocumentDto,
    _userId?: string
  ): Promise<DigitalSignature | null> {
    const signature = await this.findById(tenantId, id);
    if (!signature) return null;

    if (signature.status !== 'pending') {
      throw new Error('Signature request is not pending');
    }

    // Check expiration
    if (signature.expiresAt && signature.expiresAt < new Date()) {
      signature.status = 'expired';
      await this.repository.save(signature);
      throw new Error('Signature request has expired');
    }

    // Generate signature hash
    const dataToHash = JSON.stringify({
      documentId: signature.documentId,
      signerId: signature.signerId,
      timestamp: new Date().toISOString(),
      signatureData: dto.signatureData,
    });
    const signatureHash = crypto.createHash('sha256').update(dataToHash).digest('hex');

    signature.signatureData = dto.signatureData;
    signature.signatureHash = signatureHash;
    signature.certificateInfo = dto.certificateInfo;
    signature.certificateSerial = dto.certificateSerial;
    signature.certificateIssuer = dto.certificateIssuer;
    signature.ipAddress = dto.ipAddress;
    signature.userAgent = dto.userAgent;
    signature.geolocation = dto.geolocation;
    signature.signedAt = new Date();
    signature.status = 'signed';
    signature.isValid = true;
    signature.lastValidatedAt = new Date();

    return this.repository.save(signature);
  }

  async rejectSignature(
    tenantId: string,
    id: string,
    dto: RejectSignatureDto,
    _userId?: string
  ): Promise<DigitalSignature | null> {
    const signature = await this.findById(tenantId, id);
    if (!signature) return null;

    if (signature.status !== 'pending') {
      throw new Error('Signature request is not pending');
    }

    signature.status = 'rejected';
    signature.reason = dto.reason;

    return this.repository.save(signature);
  }

  async revokeSignature(
    tenantId: string,
    id: string,
    reason: string,
    _userId?: string
  ): Promise<DigitalSignature | null> {
    const signature = await this.findById(tenantId, id);
    if (!signature) return null;

    if (signature.status !== 'signed') {
      throw new Error('Only signed documents can be revoked');
    }

    signature.status = 'revoked';
    signature.reason = reason;
    signature.isValid = false;

    return this.repository.save(signature);
  }

  async validateSignature(
    tenantId: string,
    id: string
  ): Promise<{ isValid: boolean; errors?: string[] }> {
    const signature = await this.findById(tenantId, id);
    if (!signature) {
      return { isValid: false, errors: ['Signature not found'] };
    }

    const errors: string[] = [];

    // Check status
    if (signature.status !== 'signed') {
      errors.push(`Invalid status: ${signature.status}`);
    }

    // Check certificate validity for advanced/qualified signatures
    if (signature.signatureType !== 'simple' && signature.certificateValidTo) {
      if (signature.certificateValidTo < new Date()) {
        errors.push('Certificate has expired');
      }
    }

    // Update validation timestamp
    signature.lastValidatedAt = new Date();
    signature.validationErrors = errors.length > 0 ? { errors } : undefined;
    signature.isValid = errors.length === 0;
    await this.repository.save(signature);

    return {
      isValid: errors.length === 0,
      errors: errors.length > 0 ? errors : undefined,
    };
  }

  // ============================================
  // QUERIES
  // ============================================

  async getByDocument(tenantId: string, documentId: string): Promise<DigitalSignature[]> {
    return this.repository.find({
      where: { tenantId, documentId },
      order: { requestedAt: 'DESC' },
    });
  }

  async getBySigner(tenantId: string, signerId: string): Promise<DigitalSignature[]> {
    return this.repository.find({
      where: { tenantId, signerId },
      order: { requestedAt: 'DESC' },
    });
  }

  async getPendingSignatures(tenantId: string, signerId?: string): Promise<DigitalSignature[]> {
    const where: any = { tenantId, status: 'pending' as SignatureStatus };
    if (signerId) {
      where.signerId = signerId;
    }

    return this.repository.find({
      where,
      order: { requestedAt: 'ASC' },
    });
  }

  async getExpiredSignatures(tenantId: string): Promise<DigitalSignature[]> {
    return this.repository.find({
      where: {
        tenantId,
        status: 'pending' as SignatureStatus,
        expiresAt: LessThan(new Date()),
      },
    });
  }

  async markExpiredSignatures(tenantId: string): Promise<number> {
    const result = await this.repository.update(
      {
        tenantId,
        status: 'pending' as SignatureStatus,
        expiresAt: LessThan(new Date()),
      },
      { status: 'expired' as SignatureStatus }
    );

    return result.affected || 0;
  }

  async getStatistics(tenantId: string): Promise<{
    pending: number;
    signed: number;
    rejected: number;
    expired: number;
    revoked: number;
  }> {
    const [pending, signed, rejected, expired, revoked] = await Promise.all([
      this.repository.count({ where: { tenantId, status: 'pending' } }),
      this.repository.count({ where: { tenantId, status: 'signed' } }),
      this.repository.count({ where: { tenantId, status: 'rejected' } }),
      this.repository.count({ where: { tenantId, status: 'expired' } }),
      this.repository.count({ where: { tenantId, status: 'revoked' } }),
    ]);

    return { pending, signed, rejected, expired, revoked };
  }
}
