/**
 * Feedback Client — TypeScript SDK for user feedback with screenshots
 *
 * @module @bytelyst/feedback-client
 */

import { createApiClient, type ApiClient } from '@bytelyst/api-client';

export interface FeedbackClientConfig {
  baseUrl: string;
  getAuthToken: () => string | null;
}

export interface DeviceContext {
  osVersion: string;
  appVersion: string;
  deviceModel: string;
  screenResolution: string;
  locale: string;
}

export interface SubmitFeedbackParams {
  type: 'bug' | 'feature' | 'praise' | 'other';
  title: string;
  body?: string;
  screen?: string;
  rating?: number;
  appVersion?: string;
  platform?: 'web' | 'ios' | 'android';
  screenshot?: {
    blob: Blob;
    contentType: 'image/png' | 'image/jpeg' | 'image/webp';
  };
  deviceContext?: DeviceContext;
}

export interface SasResponse {
  blobPath: string;
  uploadUrl: string;
  expiresIn: number;
  maxSizeBytes: number;
}

export interface FeedbackResponse {
  id: string;
  productId: string;
  userId: string;
  type: string;
  title: string;
  status: string;
  createdAt: string;
  screenshotBlobPath?: string;
}

export type UploadProgressCallback = (loaded: number, total: number) => void;

export interface ScreenshotOptions {
  /** For web: CSS selector of element to capture. If omitted, captures viewport */
  selector?: string;
  /** Image format */
  format?: 'png' | 'jpeg' | 'webp';
  /** JPEG quality (0-1), only used for jpeg format */
  quality?: number;
}

export interface CaptureResult {
  blob: Blob;
  contentType: 'image/png' | 'image/jpeg' | 'image/webp';
  width: number;
  height: number;
}

/**
 * Create a feedback client for submitting user feedback with optional screenshots
 */
export function createFeedbackClient(config: FeedbackClientConfig) {
  const api = createApiClient({
    baseUrl: config.baseUrl,
    getToken: config.getAuthToken,
  });

  return new FeedbackClient(api);
}

/**
 * Feedback client class for submitting feedback with screenshots
 */
export class FeedbackClient {
  constructor(private api: ApiClient) {}

  /**
   * Submit feedback with optional screenshot
   *
   * Flow:
   * 1. If screenshot provided, get SAS URL
   * 2. Upload screenshot to blob storage
   * 3. Submit feedback with screenshot metadata
   */
  async submitWithScreenshot(
    params: SubmitFeedbackParams,
    onProgress?: UploadProgressCallback
  ): Promise<FeedbackResponse> {
    let screenshotMeta: { blobPath: string; contentType: string; sizeBytes: number } | undefined;

    // Step 1 & 2: Handle screenshot upload if provided
    if (params.screenshot) {
      const sas = await this.generateSasUrl(params.screenshot.contentType);

      await this.uploadScreenshot(sas.uploadUrl, params.screenshot.blob, onProgress);

      screenshotMeta = {
        blobPath: sas.blobPath,
        contentType: params.screenshot.contentType,
        sizeBytes: params.screenshot.blob.size,
      };
    }

    // Step 3: Submit feedback
    const response = await this.api.fetch<FeedbackResponse>('/api/feedback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        type: params.type,
        title: params.title,
        body: params.body,
        screen: params.screen,
        rating: params.rating,
        appVersion: params.appVersion,
        platform: params.platform,
        screenshotBlobPath: screenshotMeta?.blobPath,
        screenshotContentType: screenshotMeta?.contentType as
          | 'image/png'
          | 'image/jpeg'
          | 'image/webp',
        screenshotSizeBytes: screenshotMeta?.sizeBytes,
        deviceContext: params.deviceContext,
      }),
    });

    return response;
  }

  /**
   * Generate SAS URL for screenshot upload
   */
  private async generateSasUrl(
    contentType: 'image/png' | 'image/jpeg' | 'image/webp'
  ): Promise<SasResponse> {
    const response = await this.api.fetch<SasResponse>('/api/feedback/sas', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ contentType }),
    });
    return response;
  }

  /**
   * Upload screenshot directly to Azure Blob
   */
  private async uploadScreenshot(
    uploadUrl: string,
    blob: Blob,
    onProgress?: UploadProgressCallback
  ): Promise<void> {
    // Use XMLHttpRequest for progress tracking if callback provided
    if (onProgress && typeof window !== 'undefined') {
      return this.uploadWithProgress(uploadUrl, blob, onProgress);
    }

    // Simple fetch upload
    const response = await fetch(uploadUrl, {
      method: 'PUT',
      headers: {
        'Content-Type': blob.type,
        'x-ms-blob-type': 'BlockBlob',
      },
      body: blob,
    });

    if (!response.ok) {
      throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
    }
  }

  /**
   * Upload with progress tracking using XMLHttpRequest
   */
  private uploadWithProgress(
    uploadUrl: string,
    blob: Blob,
    onProgress: UploadProgressCallback
  ): Promise<void> {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();

      xhr.upload.addEventListener('progress', (event: ProgressEvent) => {
        if (event.lengthComputable) {
          onProgress(event.loaded, event.total);
        }
      });

      xhr.addEventListener('load', () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve();
        } else {
          reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
        }
      });

      xhr.addEventListener('error', () => {
        reject(new Error('Upload failed: Network error'));
      });

      xhr.open('PUT', uploadUrl);
      xhr.setRequestHeader('Content-Type', blob.type);
      xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
      xhr.send(blob);
    });
  }

  /**
   * Capture screenshot of current page or element (Web only)
   *
   * Uses native getDisplayMedia for screen capture or html2canvas-style
   * DOM serialization for element capture.
   */
  async captureScreenshot(options: ScreenshotOptions = {}): Promise<CaptureResult> {
    // Check if running in browser
    if (typeof window === 'undefined' || typeof document === 'undefined') {
      throw new Error('Screenshot capture only available in browser environment');
    }

    const format = options.format || 'png';
    const mimeType =
      format === 'png' ? 'image/png' : format === 'jpeg' ? 'image/jpeg' : 'image/webp';

    // If selector provided, capture specific element
    if (options.selector) {
      return this.captureElement(options.selector, mimeType, options.quality);
    }

    // Otherwise capture full screen using getDisplayMedia
    return this.captureScreen(mimeType);
  }

  /**
   * Capture entire screen using getDisplayMedia
   */
  private async captureScreen(mimeType: string): Promise<CaptureResult> {
    try {
      // Request screen capture permission
      const stream = await navigator.mediaDevices.getDisplayMedia({
        video: true,
        audio: false,
      });

      // Create video element to capture frame
      const video = document.createElement('video');
      video.srcObject = stream;

      // Wait for video to load
      await new Promise<void>((resolve, reject) => {
        video.onloadedmetadata = () => {
          video.play();
          resolve();
        };
        video.onerror = () => reject(new Error('Failed to load video stream'));
        // Timeout after 5 seconds
        setTimeout(() => reject(new Error('Video load timeout')), 5000);
      });

      // Draw to canvas
      const canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      const ctx = canvas.getContext('2d');
      if (!ctx) throw new Error('Failed to get canvas context');

      ctx.drawImage(video, 0, 0);

      // Stop all tracks
      stream.getTracks().forEach(track => track.stop());

      // Convert to blob
      const quality = mimeType === 'image/jpeg' ? 0.9 : undefined;
      const blob = await new Promise<Blob>((resolve, reject) => {
        canvas.toBlob(
          b => (b ? resolve(b) : reject(new Error('Canvas toBlob failed'))),
          mimeType,
          quality
        );
      });

      return {
        blob,
        contentType: mimeType as 'image/png' | 'image/jpeg' | 'image/webp',
        width: canvas.width,
        height: canvas.height,
      };
    } catch (err) {
      throw new Error(
        `Screen capture failed: ${err instanceof Error ? err.message : String(err)}`,
        { cause: err }
      );
    }
  }

  /**
   * Capture specific DOM element using html-to-image approach.
   * `mimeType` and `quality` are accepted for API parity with captureScreen()
   * but are not yet honoured here (this path always returns the fallback PNG).
   */
  private async captureElement(
    selector: string,
    _mimeType: string,
    _quality?: number
  ): Promise<CaptureResult> {
    const element = document.querySelector(selector);
    if (!element) {
      throw new Error(`Element not found: ${selector}`);
    }

    // Use html-to-image or similar approach
    // For now, we'll use a simple canvas-based approach for visible elements
    const rect = element.getBoundingClientRect();

    // Create canvas
    const canvas = document.createElement('canvas');
    canvas.width = rect.width;
    canvas.height = rect.height;
    const ctx = canvas.getContext('2d');
    if (!ctx) throw new Error('Failed to get canvas context');

    // Try to use html2canvas-style approach if available, otherwise warn
    // This is a simplified implementation
    throw new Error(
      'Element capture requires html2canvas library. ' +
        'Please install: npm install html2canvas ' +
        'Then use: html2canvas(element).then(canvas => canvas.toBlob(...))'
    );
  }

  /**
   * Capture and submit feedback in one operation
   *
   * @example
   * // Capture full screen and submit
   * const result = await client.captureAndSubmit({
   *   type: 'bug',
   *   title: 'Something is broken',
   *   body: 'Description of the issue'
   * });
   *
   * @example
   * // Capture specific element
   * const result = await client.captureAndSubmit({
   *   type: 'bug',
   *   title: 'Button not working',
   *   body: 'The submit button is unresponsive'
   * }, {
   *   selector: '#submit-button'
   * });
   */
  async captureAndSubmit(
    params: Omit<SubmitFeedbackParams, 'screenshot'>,
    screenshotOptions?: ScreenshotOptions,
    onProgress?: UploadProgressCallback
  ): Promise<FeedbackResponse> {
    // Capture screenshot
    const capture = await this.captureScreenshot(screenshotOptions);

    // Submit with captured screenshot
    return this.submitWithScreenshot(
      {
        ...params,
        screenshot: {
          blob: capture.blob,
          contentType: capture.contentType,
        },
      },
      onProgress
    );
  }
}
