import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError, map, catchError, timeout } from 'rxjs';
import { ApiService, validateId, validatePagination } from '../../../core/services/api.service';
import { RuntimeConfigService } from '../../../core/services/runtime-config.service';
import {
  ApprovalResponseDto,
  PaginatedApprovalsResponse,
  RejectionReason,
} from '../../../api/models';

export interface ApproveRequestDto {
  remarks: string;
  reviewedDocuments?: string[];
}

export interface RejectRequestDto {
  remarks: string;
  rejectionReason: RejectionReason;
}

export interface RequestChangesDto {
  remarks: string;
  requiredDocuments: string[];
}

/**
 * Backend paginated response with meta wrapper
 */
interface BackendPaginatedResponse {
  data: ApprovalResponseDto[];
  meta?: {
    total?: number;
    page?: number;
    limit?: number;
    totalPages?: number;
    hasNext?: boolean;
    hasPrev?: boolean;
  };
  // Also support flat pagination fields
  total?: number;
  page?: number;
  limit?: number;
  totalPages?: number;
  hasNextPage?: boolean;
}

/**
 * Ensures response has valid data array for paginated approvals
 * Handles both { data, meta: {...} } and { data, total, page, ... } formats
 */
function ensureValidPaginatedResponse(
  response: BackendPaginatedResponse | null | undefined,
  page: number,
  limit: number
): PaginatedApprovalsResponse {
  if (!response) {
    return {
      data: [],
      total: 0,
      page,
      limit,
      totalPages: 0,
      hasNextPage: false,
    };
  }

  // Extract pagination from meta object if present, otherwise use flat fields
  const meta = response.meta;
  const total = meta?.total ?? response.total ?? 0;
  const responsePage = meta?.page ?? response.page ?? page;
  const responseLimit = meta?.limit ?? response.limit ?? limit;
  const totalPages = meta?.totalPages ?? response.totalPages ?? 0;
  const hasNextPage = meta?.hasNext ?? response.hasNextPage ?? false;

  return {
    data: Array.isArray(response.data) ? response.data : [],
    total: typeof total === 'number' && total >= 0 ? total : 0,
    page: typeof responsePage === 'number' && responsePage >= 1 ? responsePage : page,
    limit: typeof responseLimit === 'number' && responseLimit >= 1 ? responseLimit : limit,
    totalPages: typeof totalPages === 'number' && totalPages >= 0 ? totalPages : 0,
    hasNextPage: typeof hasNextPage === 'boolean' ? hasNextPage : false,
  };
}

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

/**
 * Validates approval remarks
 */
function validateRemarks(remarks: string | undefined | null): string {
  if (remarks === undefined || remarks === null) {
    return '';
  }

  if (typeof remarks !== 'string') {
    throw new Error('Remarks must be a string');
  }

  const trimmed = remarks.trim();

  // Limit length to prevent abuse
  if (trimmed.length > 5000) {
    throw new Error('Remarks cannot exceed 5000 characters');
  }

  return trimmed;
}

/**
 * Validates document IDs array
 */
function validateDocumentIds(docs: string[] | undefined | null): string[] {
  if (!docs) {
    return [];
  }

  if (!Array.isArray(docs)) {
    throw new Error('Documents must be an array');
  }

  return docs
    .filter((id) => typeof id === 'string' && id.trim().length > 0)
    .map((id) => id.trim());
}

@Injectable({
  providedIn: 'root',
})
export class ApprovalService {
  private readonly api = inject(ApiService);
  private readonly http = inject(HttpClient);
  private readonly configService = inject(RuntimeConfigService);

  private get baseUrl(): string {
    return this.configService.apiBaseUrl;
  }

  getPendingApprovals(page = 1, limit = 10): Observable<PaginatedApprovalsResponse> {
    const validated = validatePagination(page, limit);

    // Use HttpClient directly to avoid extractData unwrapping the paginated response
    const params = new HttpParams()
      .set('page', validated.page.toString())
      .set('limit', validated.limit.toString());

    return this.http
      .get<BackendPaginatedResponse>(`${this.baseUrl}/approvals/pending`, { params })
      .pipe(
        timeout(30000),
        map((response) => ensureValidPaginatedResponse(response, validated.page, validated.limit)),
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : 'Failed to fetch pending approvals';
          return throwError(() => new Error(message));
        })
      );
  }

  getApprovalsByRequest(requestId: string): Observable<ApprovalResponseDto[]> {
    try {
      const validId = validateId(requestId, 'Request ID');

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

  getApproval(approvalId: string): Observable<ApprovalResponseDto> {
    try {
      const validId = validateId(approvalId, 'Approval ID');

      return this.api.get<ApprovalResponseDto>(`/approvals/${validId}`).pipe(
        map((response) => {
          if (!response) {
            throw new Error('Approval not found');
          }
          // Ensure nested arrays are valid
          return {
            ...response,
            reviewedDocuments: Array.isArray(response.reviewedDocuments)
              ? response.reviewedDocuments
              : [],
          };
        }),
        catchError((error: unknown) => {
          const message =
            error instanceof Error ? error.message : `Failed to fetch approval: ${approvalId}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  approve(requestId: string, dto: ApproveRequestDto): Observable<ApprovalResponseDto> {
    try {
      const validId = validateId(requestId, 'Request ID');

      if (!dto) {
        return throwError(() => new Error('Approval data is required'));
      }

      const sanitizedDto: ApproveRequestDto = {
        remarks: validateRemarks(dto.remarks),
        reviewedDocuments: validateDocumentIds(dto.reviewedDocuments),
      };

      return this.api.post<ApprovalResponseDto>(`/approvals/${validId}/approve`, sanitizedDto).pipe(
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : `Failed to approve request: ${requestId}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  reject(requestId: string, dto: RejectRequestDto): Observable<ApprovalResponseDto> {
    try {
      const validId = validateId(requestId, 'Request ID');

      if (!dto) {
        return throwError(() => new Error('Rejection data is required'));
      }

      if (!dto.rejectionReason) {
        return throwError(() => new Error('Rejection reason is required'));
      }

      const sanitizedDto = {
        remarks: validateRemarks(dto.remarks),
        reason: dto.rejectionReason, // Backend expects 'reason' not 'rejectionReason'
      };

      return this.api.post<ApprovalResponseDto>(`/approvals/${validId}/reject`, sanitizedDto).pipe(
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : `Failed to reject request: ${requestId}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  requestChanges(requestId: string, dto: RequestChangesDto): Observable<ApprovalResponseDto> {
    try {
      const validId = validateId(requestId, 'Request ID');

      if (!dto) {
        return throwError(() => new Error('Request changes data is required'));
      }

      const requiredDocuments = validateDocumentIds(dto.requiredDocuments);
      if (requiredDocuments.length === 0) {
        return throwError(() => new Error('At least one required document must be specified'));
      }

      const sanitizedDto: RequestChangesDto = {
        remarks: validateRemarks(dto.remarks),
        requiredDocuments,
      };

      return this.api
        .post<ApprovalResponseDto>(`/approvals/requests/${validId}/request-changes`, sanitizedDto)
        .pipe(
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : `Failed to request changes for: ${requestId}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  getApprovalHistory(requestId: string): Observable<ApprovalResponseDto[]> {
    try {
      const validId = validateId(requestId, 'Request ID');

      // Use the existing approvals-by-request endpoint which returns all approvals for a request
      return this.api.get<ApprovalResponseDto[]>(`/approvals/requests/${validId}`).pipe(
        map((response) => ensureValidArray(response)),
        catchError((error: unknown) => {
          const message =
            error instanceof Error ? error.message : `Failed to fetch approval history for: ${requestId}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }
}
