/**
 * Document Service
 * Logica de negocio para gestion de documentos.
 *
 * @module Documents (MAE-016)
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import {
  Document,
  DocumentType,
  DocumentStatus,
} from '../entities/document.entity';
import { DocumentCategory, AccessLevel } from '../entities/document-category.entity';
import { AccessLog } from '../entities/access-log.entity';

export interface CreateDocumentDto {
  documentCode: string;
  title: string;
  description?: string;
  categoryId?: string;
  documentType: DocumentType;
  accessLevel?: AccessLevel;
  projectId?: string;
  projectCode?: string;
  projectName?: string;
  author?: string;
  keywords?: string[];
  tags?: string[];
  documentDate?: Date;
  effectiveDate?: Date;
  expiryDate?: Date;
  source?: string;
  externalReference?: string;
  requiresApproval?: boolean;
  customFields?: Record<string, any>;
  metadata?: Record<string, any>;
}

export interface UpdateDocumentDto extends Partial<CreateDocumentDto> {
  status?: DocumentStatus;
  notes?: string;
}

export interface DocumentFilters {
  categoryId?: string;
  documentType?: DocumentType;
  status?: DocumentStatus;
  projectId?: string;
  search?: string;
  tags?: string[];
  page?: number;
  limit?: number;
}

export class DocumentService {
  constructor(
    private readonly documentRepository: Repository<Document>,
    private readonly categoryRepository: Repository<DocumentCategory>,
    private readonly accessLogRepository: Repository<AccessLog>,
  ) {}

  /**
   * Create a new document
   */
  async create(
    tenantId: string,
    dto: CreateDocumentDto,
    userId?: string,
  ): Promise<Document> {
    // Get category for default settings
    let accessLevel = dto.accessLevel || 'internal';
    let requiresApproval = dto.requiresApproval || false;

    if (dto.categoryId) {
      const category = await this.categoryRepository.findOne({
        where: { id: dto.categoryId, tenantId },
      });
      if (category) {
        accessLevel = dto.accessLevel || category.defaultAccessLevel;
        requiresApproval = dto.requiresApproval ?? category.requiresApproval;
      }
    }

    const document = this.documentRepository.create({
      tenantId,
      ...dto,
      accessLevel,
      requiresApproval,
      createdBy: userId,
    });

    return this.documentRepository.save(document);
  }

  /**
   * Find document by ID
   */
  async findById(tenantId: string, id: string): Promise<Document | null> {
    return this.documentRepository.findOne({
      where: { id, tenantId },
      relations: ['category'],
    });
  }

  /**
   * Find document by code
   */
  async findByCode(tenantId: string, code: string): Promise<Document | null> {
    return this.documentRepository.findOne({
      where: { documentCode: code, tenantId },
      relations: ['category'],
    });
  }

  /**
   * Find all documents with filters
   */
  async findAll(
    tenantId: string,
    filters: DocumentFilters,
  ): Promise<{ data: Document[]; total: number; page: number; limit: number }> {
    const page = filters.page || 1;
    const limit = filters.limit || 20;
    const skip = (page - 1) * limit;

    const queryBuilder = this.documentRepository
      .createQueryBuilder('doc')
      .leftJoinAndSelect('doc.category', 'category')
      .where('doc.tenant_id = :tenantId', { tenantId })
      .andWhere('doc.deleted_at IS NULL');

    if (filters.categoryId) {
      queryBuilder.andWhere('doc.category_id = :categoryId', {
        categoryId: filters.categoryId,
      });
    }

    if (filters.documentType) {
      queryBuilder.andWhere('doc.document_type = :documentType', {
        documentType: filters.documentType,
      });
    }

    if (filters.status) {
      queryBuilder.andWhere('doc.status = :status', { status: filters.status });
    }

    if (filters.projectId) {
      queryBuilder.andWhere('doc.project_id = :projectId', {
        projectId: filters.projectId,
      });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(doc.title ILIKE :search OR doc.document_code ILIKE :search OR doc.description ILIKE :search)',
        { search: `%${filters.search}%` },
      );
    }

    if (filters.tags && filters.tags.length > 0) {
      queryBuilder.andWhere('doc.tags && :tags', { tags: filters.tags });
    }

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

    return { data, total, page, limit };
  }

  /**
   * Update a document
   */
  async update(
    tenantId: string,
    id: string,
    dto: UpdateDocumentDto,
    userId?: string,
  ): Promise<Document | null> {
    const document = await this.findById(tenantId, id);
    if (!document) {
      return null;
    }

    Object.assign(document, dto, { updatedBy: userId });
    return this.documentRepository.save(document);
  }

  /**
   * Soft delete a document
   */
  async delete(tenantId: string, id: string): Promise<boolean> {
    const result = await this.documentRepository.update(
      { id, tenantId },
      { deletedAt: new Date() },
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Change document status
   */
  async changeStatus(
    tenantId: string,
    id: string,
    status: DocumentStatus,
    userId?: string,
  ): Promise<Document | null> {
    const document = await this.findById(tenantId, id);
    if (!document) {
      return null;
    }

    document.status = status;
    document.updatedBy = userId;

    if (status === 'approved') {
      document.approvedById = userId;
      document.approvedAt = new Date();
    }

    return this.documentRepository.save(document);
  }

  /**
   * Lock a document for editing
   */
  async lock(
    tenantId: string,
    id: string,
    userId: string,
  ): Promise<Document | null> {
    const document = await this.findById(tenantId, id);
    if (!document) {
      return null;
    }

    if (document.isLocked && document.lockedById !== userId) {
      throw new Error('Documento bloqueado por otro usuario');
    }

    document.isLocked = true;
    document.lockedById = userId;
    document.lockedAt = new Date();

    return this.documentRepository.save(document);
  }

  /**
   * Unlock a document
   */
  async unlock(
    tenantId: string,
    id: string,
    userId: string,
  ): Promise<Document | null> {
    const document = await this.findById(tenantId, id);
    if (!document) {
      return null;
    }

    if (document.lockedById !== userId) {
      throw new Error('Solo el usuario que bloqueó puede desbloquear');
    }

    document.isLocked = false;
    document.lockedById = undefined;
    document.lockedAt = undefined;

    return this.documentRepository.save(document);
  }

  /**
   * Log document access
   */
  async logAccess(
    tenantId: string,
    documentId: string,
    userId: string,
    action: string,
    options?: {
      versionId?: string;
      userName?: string;
      userIp?: string;
      userAgent?: string;
      sessionId?: string;
      requestSource?: string;
      actionDetails?: Record<string, any>;
    },
  ): Promise<AccessLog> {
    const log = this.accessLogRepository.create({
      tenantId,
      documentId,
      userId,
      action,
      versionId: options?.versionId,
      userName: options?.userName,
      userIp: options?.userIp,
      userAgent: options?.userAgent,
      sessionId: options?.sessionId,
      requestSource: options?.requestSource,
      actionDetails: options?.actionDetails,
    });

    return this.accessLogRepository.save(log);
  }

  /**
   * Get document access history
   */
  async getAccessHistory(
    tenantId: string,
    documentId: string,
    limit: number = 50,
  ): Promise<AccessLog[]> {
    return this.accessLogRepository.find({
      where: { tenantId, documentId },
      order: { accessedAt: 'DESC' },
      take: limit,
    });
  }

  /**
   * Get documents by project
   */
  async findByProject(
    tenantId: string,
    projectId: string,
    documentType?: DocumentType,
  ): Promise<Document[]> {
    const where: FindOptionsWhere<Document> = { tenantId, projectId };
    if (documentType) {
      where.documentType = documentType;
    }

    return this.documentRepository.find({
      where,
      relations: ['category'],
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Generate unique document code
   */
  async generateDocumentCode(
    tenantId: string,
    prefix: string = 'DOC',
  ): Promise<string> {
    const year = new Date().getFullYear();
    const codePrefix = `${prefix}-${year}-`;

    const lastDoc = await this.documentRepository
      .createQueryBuilder('doc')
      .where('doc.tenant_id = :tenantId', { tenantId })
      .andWhere('doc.document_code LIKE :prefix', { prefix: `${codePrefix}%` })
      .orderBy('doc.document_code', 'DESC')
      .getOne();

    let nextNumber = 1;
    if (lastDoc) {
      const match = lastDoc.documentCode.match(/\d{5}$/);
      if (match) {
        nextNumber = parseInt(match[0], 10) + 1;
      }
    }

    return `${codePrefix}${nextNumber.toString().padStart(5, '0')}`;
  }

  /**
   * Get document statistics
   */
  async getStatistics(
    tenantId: string,
    projectId?: string,
  ): Promise<{
    total: number;
    byStatus: Record<string, number>;
    byType: Record<string, number>;
    recentUploads: number;
  }> {
    const baseWhere: FindOptionsWhere<Document> = { tenantId };
    if (projectId) {
      baseWhere.projectId = projectId;
    }

    const [total, byStatusRaw, byTypeRaw] = await Promise.all([
      this.documentRepository.count({ where: baseWhere }),
      this.documentRepository
        .createQueryBuilder('doc')
        .select('doc.status', 'status')
        .addSelect('COUNT(*)', 'count')
        .where('doc.tenant_id = :tenantId', { tenantId })
        .andWhere(projectId ? 'doc.project_id = :projectId' : '1=1', { projectId })
        .andWhere('doc.deleted_at IS NULL')
        .groupBy('doc.status')
        .getRawMany(),
      this.documentRepository
        .createQueryBuilder('doc')
        .select('doc.document_type', 'type')
        .addSelect('COUNT(*)', 'count')
        .where('doc.tenant_id = :tenantId', { tenantId })
        .andWhere(projectId ? 'doc.project_id = :projectId' : '1=1', { projectId })
        .andWhere('doc.deleted_at IS NULL')
        .groupBy('doc.document_type')
        .getRawMany(),
    ]);

    // Count documents uploaded in last 7 days
    const sevenDaysAgo = new Date();
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

    const recentUploads = await this.documentRepository
      .createQueryBuilder('doc')
      .where('doc.tenant_id = :tenantId', { tenantId })
      .andWhere(projectId ? 'doc.project_id = :projectId' : '1=1', { projectId })
      .andWhere('doc.created_at >= :date', { date: sevenDaysAgo })
      .andWhere('doc.deleted_at IS NULL')
      .getCount();

    const byStatus: Record<string, number> = {};
    byStatusRaw.forEach((row) => {
      byStatus[row.status] = parseInt(row.count, 10);
    });

    const byType: Record<string, number> = {};
    byTypeRaw.forEach((row) => {
      byType[row.type] = parseInt(row.count, 10);
    });

    return { total, byStatus, byType, recentUploads };
  }
}
