import { Injectable, inject } from '@angular/core';
import { Observable, throwError, map, catchError } from 'rxjs';
import { ApiService, validateId, validatePagination } from '../../../core/services/api.service';
import {
  WebhookResponseDto,
  CreateWebhookDto,
  UpdateWebhookDto,
  WebhookTestResultDto,
  WebhookLogEntryDto,
  PaginatedWebhookLogsResponse,
  WebhookEvent,
} from '../../../api/models';

// Valid webhook events from the model
const VALID_WEBHOOK_EVENTS: WebhookEvent[] = [
  'APPROVAL_REQUIRED',
  'DOCUMENT_UPDATED',
  'REQUEST_APPROVED',
  'REQUEST_REJECTED',
  'CHANGES_REQUESTED',
  'LICENSE_MINTED',
  'LICENSE_REVOKED',
];

/**
 * Validates URL format
 */
function validateUrl(url: string | undefined | null, fieldName = 'URL'): string {
  if (!url || typeof url !== 'string') {
    throw new Error(`${fieldName} is required`);
  }

  const trimmed = url.trim();

  if (trimmed.length === 0) {
    throw new Error(`${fieldName} cannot be empty`);
  }

  // Basic URL validation
  try {
    const parsedUrl = new URL(trimmed);
    // Only allow http and https protocols
    if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
      throw new Error(`${fieldName} must use HTTP or HTTPS protocol`);
    }
    return trimmed;
  } catch (error) {
    if (error instanceof Error && error.message.includes('protocol')) {
      throw error;
    }
    throw new Error(`${fieldName} is not a valid URL`);
  }
}

/**
 * Validates webhook events array
 */
function validateEvents(events: WebhookEvent[] | string[] | undefined | null): WebhookEvent[] {
  if (!events) {
    return [];
  }

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

  const validated = events.filter(
    (event): event is WebhookEvent =>
      typeof event === 'string' && VALID_WEBHOOK_EVENTS.includes(event as WebhookEvent)
  );
  return validated as WebhookEvent[];
}

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

/**
 * Ensures paginated response is valid
 */
function ensureValidPaginatedResponse(
  response: PaginatedWebhookLogsResponse | null | undefined,
  page: number,
  limit: number
): PaginatedWebhookLogsResponse {
  if (!response) {
    return {
      data: [],
      total: 0,
      page,
      limit,
      totalPages: 0,
      hasNextPage: false,
    };
  }

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

/**
 * Validates create webhook DTO
 */
function validateCreateWebhookDto(dto: CreateWebhookDto | null | undefined): CreateWebhookDto {
  if (!dto) {
    throw new Error('Webhook data is required');
  }

  const url = validateUrl(dto.url, 'Webhook URL');
  const events = validateEvents(dto.events);

  if (events.length === 0) {
    throw new Error('At least one event must be specified');
  }

  return {
    url,
    events,
    description: dto.description?.trim() || undefined,
  };
}

/**
 * Validates update webhook DTO
 */
function validateUpdateWebhookDto(dto: UpdateWebhookDto | null | undefined): UpdateWebhookDto {
  if (!dto) {
    throw new Error('Update data is required');
  }

  const sanitized: UpdateWebhookDto = {};

  if (dto.url !== undefined) {
    sanitized.url = validateUrl(dto.url, 'Webhook URL');
  }

  if (dto.events !== undefined) {
    sanitized.events = validateEvents(dto.events);
  }

  if (dto.description !== undefined) {
    sanitized.description = typeof dto.description === 'string' ? dto.description.trim() : undefined;
  }

  if (dto.isActive !== undefined) {
    if (typeof dto.isActive !== 'boolean') {
      throw new Error('isActive must be a boolean');
    }
    sanitized.isActive = dto.isActive;
  }

  return sanitized;
}

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

  getWebhooks(): Observable<WebhookResponseDto[]> {
    return this.api.get<WebhookResponseDto[]>('/webhooks').pipe(
      map((response) => ensureValidArray(response)),
      catchError((error: unknown) => {
        const message = error instanceof Error ? error.message : 'Failed to fetch webhooks';
        return throwError(() => new Error(message));
      })
    );
  }

  getWebhook(id: string): Observable<WebhookResponseDto> {
    try {
      const validId = validateId(id, 'Webhook ID');

      return this.api.get<WebhookResponseDto>(`/webhooks/${validId}`).pipe(
        map((response) => {
          if (!response) {
            throw new Error('Webhook not found');
          }
          return {
            ...response,
            events: Array.isArray(response.events) ? response.events : [],
          };
        }),
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : `Failed to fetch webhook: ${id}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  createWebhook(dto: CreateWebhookDto): Observable<WebhookResponseDto> {
    try {
      const sanitizedDto = validateCreateWebhookDto(dto);

      return this.api.post<WebhookResponseDto>('/webhooks', sanitizedDto).pipe(
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : 'Failed to create webhook';
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  updateWebhook(id: string, dto: UpdateWebhookDto): Observable<WebhookResponseDto> {
    try {
      const validId = validateId(id, 'Webhook ID');
      const sanitizedDto = validateUpdateWebhookDto(dto);

      return this.api.patch<WebhookResponseDto>(`/webhooks/${validId}`, sanitizedDto).pipe(
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : `Failed to update webhook: ${id}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  deleteWebhook(id: string): Observable<void> {
    try {
      const validId = validateId(id, 'Webhook ID');

      return this.api.delete<void>(`/webhooks/${validId}`).pipe(
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : `Failed to delete webhook: ${id}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  testWebhook(id: string): Observable<WebhookTestResultDto> {
    try {
      const validId = validateId(id, 'Webhook ID');

      return this.api.post<WebhookTestResultDto>(`/webhooks/${validId}/test`, {}).pipe(
        map((response) => {
          if (!response) {
            return {
              success: false,
              statusCode: 0,
              statusMessage: 'No response received',
              responseTime: 0,
              error: 'No response from server',
            };
          }
          return {
            success: typeof response.success === 'boolean' ? response.success : false,
            statusCode: typeof response.statusCode === 'number' ? response.statusCode : 0,
            statusMessage:
              typeof response.statusMessage === 'string' ? response.statusMessage : 'Unknown',
            responseTime: typeof response.responseTime === 'number' ? response.responseTime : 0,
            error: response.error,
          };
        }),
        catchError((error: unknown) => {
          const message = error instanceof Error ? error.message : `Failed to test webhook: ${id}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }

  getWebhookLogs(id: string, page = 1, limit = 20): Observable<PaginatedWebhookLogsResponse> {
    try {
      const validId = validateId(id, 'Webhook ID');
      const validated = validatePagination(page, limit);

      return this.api
        .get<PaginatedWebhookLogsResponse>(`/webhooks/${validId}/logs`, {
          page: validated.page,
          limit: validated.limit,
        })
        .pipe(
          map((response) => ensureValidPaginatedResponse(response, validated.page, validated.limit)),
          catchError((error: unknown) => {
            const message =
              error instanceof Error ? error.message : `Failed to fetch logs for webhook: ${id}`;
            return throwError(() => new Error(message));
          })
        );
    } catch (error) {
      return throwError(() => error);
    }
  }

  toggleActive(id: string, isActive: boolean): Observable<WebhookResponseDto> {
    try {
      const validId = validateId(id, 'Webhook ID');

      if (typeof isActive !== 'boolean') {
        return throwError(() => new Error('isActive must be a boolean value'));
      }

      return this.api.patch<WebhookResponseDto>(`/webhooks/${validId}`, { isActive }).pipe(
        catchError((error: unknown) => {
          const message =
            error instanceof Error
              ? error.message
              : `Failed to toggle active status for webhook: ${id}`;
          return throwError(() => new Error(message));
        })
      );
    } catch (error) {
      return throwError(() => error);
    }
  }
}
