import { Page, expect } from '@playwright/test';
import { isValidPrice, isValidDate, parsePrice, approximatelyEqual } from './test-helpers';

/**
 * Custom Assertions for Business Rules
 * 
 * Provides custom assertion helpers for:
 * - Business rule validation
 * - Session state verification
 * - Data integrity checks
 * - Cart and order validation
 */

// ============================================
// BUSINESS RULE ASSERTIONS
// ============================================

/**
 * Expect valid price format
 */
export async function expectValidPrice(page: Page, selector: string): Promise<void> {
  const element = page.locator(selector);
  const priceText = await element.textContent();
  
  if (!isValidPrice(priceText)) {
    throw new Error(`Invalid price format: ${priceText}`);
  }
}

/**
 * Expect valid date format
 */
export async function expectValidDate(page: Page, selector: string): Promise<void> {
  const element = page.locator(selector);
  const dateText = await element.textContent();
  
  if (!isValidDate(dateText)) {
    throw new Error(`Invalid date format: ${dateText}`);
  }
}

/**
 * Expect price to be within range
 */
export async function expectPriceInRange(
  page: Page,
  selector: string,
  min: number,
  max: number
): Promise<void> {
  const element = page.locator(selector);
  const priceText = await element.textContent();
  const price = parsePrice(priceText);
  
  if (price < min || price > max) {
    throw new Error(`Price ${price} is not within range ${min}-${max}`);
  }
}

/**
 * Expect two prices to be approximately equal
 */
export async function expectPricesEqual(
  actual: number,
  expected: number,
  tolerance: number = 0.01
): Promise<void> {
  if (!approximatelyEqual(actual, expected, tolerance)) {
    throw new Error(
      `Prices not equal: actual=${actual}, expected=${expected}, tolerance=${tolerance}`
    );
  }
}

// ============================================
// SESSION ASSERTIONS
// ============================================

/**
 * Expect user to be authenticated
 */
export async function expectAuthenticated(page: Page): Promise<void> {
  // Check for common authentication indicators
  // Based on header.php: user avatar, dropdown toggle with avatar, user fullname, or Profile link in dropdown
  const indicators = [
    // Avatar image (most reliable - always visible when authenticated)
    page.locator('img.avatar, .avatar'),
    // User dropdown toggle with avatar
    page.locator('a.nav-link.dropdown-toggle:has(img.avatar)'),
    // User fullname text (displayed when authenticated)
    page.locator('.fw-500.text-primary'), // This is the fullname span
    // Profile link in dropdown (may need to open dropdown, but try anyway)
    page.locator('a.dropdown-item:has-text("Profile")'),
    // Legacy fallbacks
    page.locator('a:has-text("Logout")'),
    page.locator('.user-menu'),
    page.locator('[data-user-authenticated="true"]')
  ];
  
  let found = false;
  for (const indicator of indicators) {
    if (await indicator.isVisible({ timeout: 5000 }).catch(() => false)) {
      found = true;
      break;
    }
  }
  
  if (!found) {
    throw new Error('User does not appear to be authenticated');
  }
}

/**
 * Expect session to be valid (not on login page)
 */
export async function expectSessionValid(page: Page): Promise<void> {
  const currentUrl = page.url();
  
  // Check we're not redirected to login
  if (currentUrl.includes('/login') || currentUrl === '/' + await page.evaluate(() => window.location.origin)) {
    throw new Error('Session appears invalid - redirected to login');
  }
  
  // Check for login form elements (shouldn't be present)
  const loginForm = page.locator('input[type="email"][name*="email"], input[placeholder*="email"]');
  const isLoginFormVisible = await loginForm.isVisible({ timeout: 2000 }).catch(() => false);
  
  if (isLoginFormVisible) {
    throw new Error('Session appears invalid - login form is visible');
  }
}

/**
 * Expect to be on specific page
 */
export async function expectOnPage(page: Page, urlPattern: string | RegExp): Promise<void> {
  const currentUrl = page.url();
  
  if (typeof urlPattern === 'string') {
    if (!currentUrl.includes(urlPattern)) {
      throw new Error(`Not on expected page. Expected URL to contain: ${urlPattern}, Got: ${currentUrl}`);
    }
  } else {
    if (!urlPattern.test(currentUrl)) {
      throw new Error(`Not on expected page. Expected URL to match: ${urlPattern}, Got: ${currentUrl}`);
    }
  }
}

// ============================================
// CART ASSERTIONS
// ============================================

/**
 * Expect cart to have specific number of items
 */
export async function expectCartItemCount(page: Page, expectedCount: number): Promise<void> {
  const cartRows = page.locator('#cartTable tbody tr:not(.dataTables_empty)');
  const actualCount = await cartRows.count();
  
  if (actualCount !== expectedCount) {
    throw new Error(`Cart item count mismatch. Expected: ${expectedCount}, Got: ${actualCount}`);
  }
}

/**
 * Expect cart to be empty
 */
export async function expectCartEmpty(page: Page): Promise<void> {
  await expectCartItemCount(page, 0);
}

/**
 * Expect cart to have items
 */
export async function expectCartNotEmpty(page: Page): Promise<void> {
  const cartRows = page.locator('#cartTable tbody tr:not(.dataTables_empty)');
  const count = await cartRows.count();
  
  if (count === 0) {
    throw new Error('Expected cart to have items but it is empty');
  }
}

/**
 * Expect cart total to be accurate
 */
export async function expectCartTotalAccurate(
  page: Page,
  expectedTotal: number,
  tolerance: number = 0.01
): Promise<void> {
  const totalElement = page.locator('#currentcartTotalPrice');
  const totalText = await totalElement.textContent();
  const actualTotal = parsePrice(totalText);
  
  if (!approximatelyEqual(actualTotal, expectedTotal, tolerance)) {
    throw new Error(
      `Cart total mismatch. Expected: ${expectedTotal}, Got: ${actualTotal}, Tolerance: ${tolerance}`
    );
  }
}

/**
 * Expect cart calculations to be valid (VAT, totals, etc.)
 */
export async function expectCartCalculationsValid(page: Page): Promise<void> {
  // Get totals
  const totalInclElement = page.locator('#currentcartTotalPrice');
  const totalExclElement = page.locator('#currentcartTotalPriceExcl');
  
  const totalInclText = await totalInclElement.textContent();
  const totalExclText = await totalExclElement.textContent();
  
  const totalIncl = parsePrice(totalInclText);
  const totalExcl = parsePrice(totalExclText);
  
  // VAT should be positive
  const vat = totalIncl - totalExcl;
  if (vat < 0) {
    throw new Error(`Invalid VAT calculation: ${vat}`);
  }
  
  // Total incl should be greater than total excl
  if (totalIncl < totalExcl) {
    throw new Error(`Total including VAT (${totalIncl}) is less than total excluding VAT (${totalExcl})`);
  }
  
  // VAT rate should be reasonable (between 0% and 50%)
  if (totalExcl > 0) {
    const vatRate = (vat / totalExcl) * 100;
    if (vatRate < 0 || vatRate > 50) {
      throw new Error(`VAT rate appears unreasonable: ${vatRate}%`);
    }
  }
}

// ============================================
// DATA INTEGRITY ASSERTIONS
// ============================================

/**
 * Expect element to contain specific text
 */
export async function expectElementText(
  page: Page,
  selector: string,
  expectedText: string,
  exact: boolean = false
): Promise<void> {
  const element = page.locator(selector);
  const actualText = await element.textContent();
  
  if (exact) {
    if (actualText !== expectedText) {
      throw new Error(`Text mismatch. Expected: "${expectedText}", Got: "${actualText}"`);
    }
  } else {
    if (!actualText?.includes(expectedText)) {
      throw new Error(`Text does not contain expected value. Expected to contain: "${expectedText}", Got: "${actualText}"`);
    }
  }
}

/**
 * Expect element to have specific attribute value
 */
export async function expectElementAttribute(
  page: Page,
  selector: string,
  attribute: string,
  expectedValue: string
): Promise<void> {
  const element = page.locator(selector);
  const actualValue = await element.getAttribute(attribute);
  
  if (actualValue !== expectedValue) {
    throw new Error(
      `Attribute ${attribute} mismatch. Expected: "${expectedValue}", Got: "${actualValue}"`
    );
  }
}

/**
 * Expect table to have specific row count
 */
export async function expectTableRowCount(
  page: Page,
  tableSelector: string,
  expectedCount: number
): Promise<void> {
  const rows = page.locator(`${tableSelector} tbody tr:not(.dataTables_empty)`);
  const actualCount = await rows.count();
  
  if (actualCount !== expectedCount) {
    throw new Error(`Table row count mismatch. Expected: ${expectedCount}, Got: ${actualCount}`);
  }
}

// ============================================
// ORDER ASSERTIONS
// ============================================

/**
 * Expect order to have valid status
 */
export async function expectValidOrderStatus(status: string | null): Promise<void> {
  const validStatuses = [
    'pending',
    'processing',
    'shipped',
    'delivered',
    'cancelled',
    'completed',
    'confirmed',
    'in progress'
  ];
  
  if (!status) {
    throw new Error('Order status is null or empty');
  }
  
  const normalizedStatus = status.toLowerCase().trim();
  const isValid = validStatuses.some(validStatus => 
    normalizedStatus.includes(validStatus)
  );
  
  if (!isValid) {
    throw new Error(`Invalid order status: ${status}`);
  }
}

/**
 * Expect order data to be complete
 */
export async function expectOrderDataComplete(orderData: {
  id?: string;
  status?: string;
  date?: string;
  total?: string;
}): Promise<void> {
  const missingFields: string[] = [];
  
  if (!orderData.id) missingFields.push('id');
  if (!orderData.status) missingFields.push('status');
  if (!orderData.date) missingFields.push('date');
  
  if (missingFields.length > 0) {
    throw new Error(`Order data incomplete. Missing fields: ${missingFields.join(', ')}`);
  }
}

// ============================================
// MODAL ASSERTIONS
// ============================================

/**
 * Expect modal to be visible
 */
export async function expectModalVisible(page: Page, modalSelector: string = '.modal, .swal2-popup'): Promise<void> {
  const modal = page.locator(modalSelector);
  await expect(modal).toBeVisible({ timeout: 5000 });
}

/**
 * Expect modal to be hidden
 */
export async function expectModalHidden(page: Page, modalSelector: string = '.modal, .swal2-popup'): Promise<void> {
  const modal = page.locator(modalSelector);
  await expect(modal).toBeHidden({ timeout: 5000 });
}

// ============================================
// FORM VALIDATION ASSERTIONS
// ============================================

/**
 * Expect form field to have error
 */
export async function expectFieldError(page: Page, fieldSelector: string): Promise<void> {
  // Check for common error indicators
  const field = page.locator(fieldSelector);
  const hasErrorClass = await field.evaluate(el => 
    el.classList.contains('is-invalid') || 
    el.classList.contains('error') ||
    el.classList.contains('has-error')
  );
  
  if (!hasErrorClass) {
    // Check for error message nearby
    const errorMessage = page.locator(`${fieldSelector} ~ .error, ${fieldSelector} ~ .invalid-feedback`);
    const hasErrorMessage = await errorMessage.isVisible().catch(() => false);
    
    if (!hasErrorMessage) {
      throw new Error(`Field ${fieldSelector} does not have error indication`);
    }
  }
}

/**
 * Expect form to be valid (no errors)
 */
export async function expectFormValid(page: Page, formSelector: string): Promise<void> {
  const errorElements = page.locator(`${formSelector} .is-invalid, ${formSelector} .error, ${formSelector} .has-error`);
  const errorCount = await errorElements.count();
  
  if (errorCount > 0) {
    throw new Error(`Form has ${errorCount} validation errors`);
  }
}

