import { Injectable, inject } from '@angular/core';
import { Observable, throwError, map, catchError } from 'rxjs';
import { ApiService, UploadProgress, validateId } from '../../../core/services/api.service';
import {
  DocumentResponseDto,
  DocumentVersionResponseDto,
  DownloadUrlResponseDto,
  DocumentType,
} from '../../../api/models';

// File validation constants
const MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
const ALLOWED_MIME_TYPES = [
  'application/pdf',
  'image/jpeg',
  'image/png',
  'image/gif',
  'image/webp',
  'application/msword',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.ms-excel',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'text/plain',
];

const ALLOWED_EXTENSIONS = [
  '.pdf',
  '.jpg',
  '.jpeg',
  '.png',
  '.gif',
  '.webp',
  '.doc',
  '.docx',
  '.xls',
  '.xlsx',
  '.txt',
];

/**
 * Validates a file before upload
 */
function validateFile(file: File | null | undefined): File {
  if (!file) {
    throw new Error('File is required');
  }

  if (!(file instanceof File)) {
    throw new Error('Invalid file object');
  }

  // Check file size
  if (file.size === 0) {
    throw new Error('File is empty');
  }

  if (file.size > MAX_FILE_SIZE_BYTES) {
    const maxSizeMB = MAX_FILE_SIZE_BYTES / (1024 * 1024);
    throw new Error(`File size exceeds maximum allowed size of ${maxSizeMB}MB`);
  }

  // Check MIME type
  if (file.type && !ALLOWED_MIME_TYPES.includes(file.type)) {
    throw new Error(`File type '${file.type}' is not allowed`);
  }

  // Check file extension
  const fileName = file.name || '';
  const extension = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));

  if (!ALLOWED_EXTENSIONS.includes(extension)) {
    throw new Error(`File extension '${extension}' is not allowed`);
  }

  // Sanitize filename - prevent path traversal
  if (fileName.includes('..') || fileName.includes('/') || fileName.includes('\\')) {
    throw new Error('Invalid filename');
  }

  return file;
}

/**
 * Validates document type
 */
function validateDocType(docType: DocumentType | string | undefined | null): DocumentType {
  if (!docType) {
    throw new Error('Document type is required');
  }

  const validDocTypes: DocumentType[] = [
    'FLOOR_PLAN',
    'PHOTOGRAPH',
    'ID_PROOF',
    'ADDRESS_PROOF',
    'NOC',
    'LICENSE_COPY',
    'OTHER',
    'FIRE_SAFETY',
    'HEALTH_CERT',
    'TAX_CLEARANCE',
    'SITE_PLAN',
    'BUILDING_PERMIT',
    'BUSINESS_LICENSE',
  ];

  if (!validDocTypes.includes(docType as DocumentType)) {
    throw new Error(`Invalid document type: ${docType}`);
  }

  return docType as DocumentType;
}

/**
 * Sanitizes description text
 */
function sanitizeDescription(description: string | undefined | null): string | undefined {
  if (!description) {
    return undefined;
  }

  if (typeof description !== 'string') {
    return undefined;
  }

  const trimmed = description.trim();

  if (trimmed.length === 0) {
    return undefined;
  }

  // Limit length
  if (trimmed.length > 1000) {
    throw new Error('Description cannot exceed 1000 characters');
  }

  return trimmed;
}

/**
 * Ensures array response is valid
 */
function ensureValidArray<T>(response: T[] | null | undefined): T[] {
  return Array.isArray(response) ? response : [];
}

@Injectable({
  providedIn: 'root',
})
export class DocumentService {
  private readonly api = inject(ApiService);

  getDocuments(requestId: string): Observable<DocumentResponseDto[]> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');

      return this.api.get<DocumentResponseDto[]>(`/requests/${validRequestId}/documents`).pipe(
        map((response) => ensureValidArray(response)),
        catchError((error: unknown) => {
          const message =
            error instanceof Error ? error.message : `Failed to fetch documents for request: ${requestId}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  getDocument(requestId: string, documentId: string): Observable<DocumentResponseDto> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validDocumentId = validateId(documentId, 'Document ID');

      return this.api
        .get<DocumentResponseDto>(`/requests/${validRequestId}/documents/${validDocumentId}`)
        .pipe(
          map((response) => {
            if (!response) {
              throw new Error('Document not found');
            }
            return response;
          }),
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : `Failed to fetch document: ${documentId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  getDocumentVersions(requestId: string, documentId: string): Observable<DocumentVersionResponseDto[]> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validDocumentId = validateId(documentId, 'Document ID');

      return this.api
        .get<DocumentVersionResponseDto[]>(
          `/requests/${validRequestId}/documents/${validDocumentId}/versions`
        )
        .pipe(
          map((response) => ensureValidArray(response)),
          catchError((error: unknown) => {
            const message =
              error instanceof Error
                ? error.message
                : `Failed to fetch versions for document: ${documentId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  uploadDocument(
    requestId: string,
    file: File,
    docType: DocumentType,
    description?: string
  ): Observable<DocumentResponseDto> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validFile = validateFile(file);
      const validDocType = validateDocType(docType);
      const sanitizedDescription = sanitizeDescription(description);

      const formData = new FormData();
      formData.append('file', validFile);
      formData.append('docType', validDocType);

      if (sanitizedDescription) {
        formData.append('description', sanitizedDescription);
      }

      return this.api
        .upload<DocumentResponseDto>(`/requests/${validRequestId}/documents`, formData)
        .pipe(
          catchError((error: unknown) => {
            const message = error instanceof Error ? error.message : 'Failed to upload document';
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  /**
   * Upload document with progress tracking
   */
  uploadDocumentWithProgress(
    requestId: string,
    file: File,
    docType: DocumentType,
    description?: string
  ): Observable<UploadProgress<DocumentResponseDto>> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validFile = validateFile(file);
      const validDocType = validateDocType(docType);
      const sanitizedDescription = sanitizeDescription(description);

      const formData = new FormData();
      formData.append('file', validFile);
      formData.append('docType', validDocType);

      if (sanitizedDescription) {
        formData.append('description', sanitizedDescription);
      }

      return this.api
        .uploadWithProgress<DocumentResponseDto>(`/requests/${validRequestId}/documents`, formData)
        .pipe(
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : 'Failed to upload document with progress';
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  updateDocument(requestId: string, documentId: string, file: File): Observable<DocumentResponseDto> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validDocumentId = validateId(documentId, 'Document ID');
      const validFile = validateFile(file);

      const formData = new FormData();
      formData.append('file', validFile);

      return this.api
        .upload<DocumentResponseDto>(
          `/requests/${validRequestId}/documents/${validDocumentId}`,
          formData
        )
        .pipe(
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : `Failed to update document: ${documentId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  deleteDocument(requestId: string, documentId: string): Observable<void> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validDocumentId = validateId(documentId, 'Document ID');

      return this.api
        .delete<void>(`/requests/${validRequestId}/documents/${validDocumentId}`)
        .pipe(
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : `Failed to delete document: ${documentId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  getDownloadUrl(requestId: string, documentId: string): Observable<DownloadUrlResponseDto> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validDocumentId = validateId(documentId, 'Document ID');

      return this.api
        .get<DownloadUrlResponseDto>(
          `/requests/${validRequestId}/documents/${validDocumentId}/download`
        )
        .pipe(
          map((response) => {
            if (!response || !response.url) {
              throw new Error('Invalid download URL response');
            }
            return response;
          }),
          catchError((error: unknown) => {
            const message =
              error instanceof Error
                ? error.message
                : `Failed to get download URL for document: ${documentId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  verifyDocument(requestId: string, documentId: string): Observable<{ verified: boolean }> {
    try {
      const validRequestId = validateId(requestId, 'Request ID');
      const validDocumentId = validateId(documentId, 'Document ID');

      return this.api
        .get<{ verified: boolean }>(
          `/requests/${validRequestId}/documents/${validDocumentId}/verify`
        )
        .pipe(
          map((response) => {
            if (!response) {
              return { verified: false };
            }
            return {
              verified: typeof response.verified === 'boolean' ? response.verified : false,
            };
          }),
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : `Failed to verify document: ${documentId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }
}
