import { test, expect } from '@playwright/test';

const pages = [
  { path: '/', name: 'Home' },
  { path: '/about', name: 'About' },
  { path: '/services', name: 'Services' },
  { path: '/portfolio', name: 'Portfolio' },
  { path: '/blog', name: 'Blog' },
  { path: '/contact', name: 'Contact' },
  { path: '/privacy', name: 'Privacy Policy' },
  { path: '/terms', name: 'Terms of Service' },
  { path: '/sitemap', name: 'Sitemap' },
];

// ============================================================
// WCAG 2.1 AA - 1.1.1 Non-text Content
// ============================================================
test.describe('1.1.1 Non-text Content (Images & Icons)', () => {
  for (const page of pages) {
    test(`${page.name}: all images have alt text or role="presentation"`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const images = await browserPage.locator('img').all();
      for (const img of images) {
        const alt = await img.getAttribute('alt');
        const role = await img.getAttribute('role');
        const hasAccessibleName = alt !== null || role === 'presentation';
        expect(hasAccessibleName, `Image missing alt text on ${page.path}`).toBeTruthy();
      }
    });

    test(`${page.name}: SVGs are either decorative (aria-hidden) or have accessible labels`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const svgs = await browserPage.locator('svg').all();
      for (const svg of svgs) {
        const ariaHidden = await svg.getAttribute('aria-hidden');
        const ariaLabel = await svg.getAttribute('aria-label');
        const ariaLabelledby = await svg.getAttribute('aria-labelledby');
        const role = await svg.getAttribute('role');
        const title = await svg.locator('title').count();

        // SVG must be either: hidden from AT or have accessible label
        const isAccessible =
          ariaHidden === 'true' ||
          ariaLabel !== null ||
          ariaLabelledby !== null ||
          (role === 'img' && title > 0);

        expect(isAccessible, `SVG without aria-hidden or accessible label on ${page.path}`).toBeTruthy();
      }
    });
  }
});

// ============================================================
// WCAG 2.1 AA - 1.3.1 Info and Relationships (Structure)
// ============================================================
test.describe('1.3.1 Info and Relationships (Document Structure)', () => {
  for (const page of pages) {
    test(`${page.name}: has exactly one h1 heading`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const h1Count = await browserPage.locator('h1').count();
      expect(h1Count, `${page.path} should have exactly one h1`).toBeGreaterThanOrEqual(1);
      expect(h1Count, `${page.path} should not have more than one h1`).toBeLessThanOrEqual(2);
    });

    test(`${page.name}: has semantic landmarks (header, main, footer)`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      await expect(browserPage.locator('header').first()).toBeVisible();
      await expect(browserPage.locator('main').first()).toBeVisible();
      await expect(browserPage.locator('footer').first()).toBeVisible();
    });

    test(`${page.name}: form inputs have associated labels`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const inputs = await browserPage.locator('input:not([type="hidden"]):not([type="submit"]):not([type="button"])').all();
      for (const input of inputs) {
        const id = await input.getAttribute('id');
        const ariaLabel = await input.getAttribute('aria-label');
        const ariaLabelledby = await input.getAttribute('aria-labelledby');

        let hasLabel = ariaLabel !== null || ariaLabelledby !== null;

        if (id && !hasLabel) {
          const labelCount = await browserPage.locator(`label[for="${id}"]`).count();
          hasLabel = labelCount > 0;
        }

        expect(hasLabel, `Input ${id || 'unknown'} on ${page.path} missing label`).toBeTruthy();
      }
    });

    test(`${page.name}: lists use proper list elements`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      // Navigation should use list elements
      const navLists = await browserPage.locator('nav ul, nav ol').count();
      // At least one nav should contain a list (header nav)
      if (await browserPage.locator('nav').count() > 0) {
        expect(navLists).toBeGreaterThanOrEqual(0); // soft check - not all navs require lists
      }
    });
  }
});

// ============================================================
// WCAG 2.1 AA - 1.3.3 Sensory Characteristics
// ============================================================
test.describe('1.3.3 Required fields are not indicated by color alone', () => {
  test('Contact form: required fields have text indicator beyond asterisk color', async ({ page }) => {
    await page.goto('/contact');

    // Check that required fields use aria-required attribute
    const requiredInputs = await page.locator('input[required], select[required], textarea[required]').all();
    expect(requiredInputs.length).toBeGreaterThan(0);

    for (const input of requiredInputs) {
      const ariaRequired = await input.getAttribute('aria-required');
      const required = await input.getAttribute('required');
      expect(required !== null || ariaRequired === 'true', 'Required field must have required or aria-required attribute').toBeTruthy();
    }
  });
});

// ============================================================
// WCAG 2.1 AA - 1.4.3 Contrast (Minimum)
// ============================================================
test.describe('1.4.3 Color Contrast', () => {
  test('Main text has defined color styles', async ({ page }) => {
    await page.goto('/');

    const heading = page.locator('h1').first();
    if (await heading.isVisible()) {
      const color = await heading.evaluate((el) => window.getComputedStyle(el).color);
      expect(color).toBeDefined();
      expect(color).not.toBe('');
    }
  });

  test('Body text has defined color styles', async ({ page }) => {
    await page.goto('/');

    const paragraph = page.locator('p').first();
    if (await paragraph.isVisible()) {
      const styles = await paragraph.evaluate((el) => {
        const style = window.getComputedStyle(el);
        return { color: style.color, backgroundColor: style.backgroundColor };
      });
      expect(styles.color).toBeDefined();
      expect(styles.color).not.toBe('');
    }
  });

  test('Buttons have distinct background colors', async ({ page }) => {
    await page.goto('/contact');

    const submitBtn = page.locator('button[type="submit"]').first();
    if (await submitBtn.isVisible()) {
      const bgColor = await submitBtn.evaluate((el) => window.getComputedStyle(el).backgroundColor);
      // Should not be transparent
      expect(bgColor).not.toBe('rgba(0, 0, 0, 0)');
      expect(bgColor).not.toBe('transparent');
    }
  });
});

// ============================================================
// WCAG 2.1 AA - 2.1.1 Keyboard Navigation
// ============================================================
test.describe('2.1.1 Keyboard Navigation', () => {
  test('Tab navigation reaches interactive elements', async ({ page }) => {
    await page.goto('/');

    await page.locator('body').focus();
    await page.keyboard.press('Tab');

    const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
    expect(['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(focusedElement || '')).toBeTruthy();
  });

  test('Skip link is the first focusable element', async ({ page }) => {
    await page.goto('/');

    await page.keyboard.press('Tab');

    const focusedElement = await page.evaluate(() => {
      const el = document.activeElement as HTMLElement;
      return {
        tagName: el?.tagName,
        href: el?.getAttribute('href'),
        text: el?.textContent?.trim(),
      };
    });

    // Skip link should be focused first
    expect(focusedElement.tagName).toBe('A');
    expect(focusedElement.href).toContain('#main-content');
  });

  test('Skip link works - activating it focuses main content', async ({ page }) => {
    await page.goto('/');

    // Tab to skip link
    await page.keyboard.press('Tab');

    // Activate skip link
    await page.keyboard.press('Enter');

    // Main content should be focused
    const focusedId = await page.evaluate(() => document.activeElement?.id);
    expect(focusedId).toBe('main-content');
  });

  test('Mobile menu opens and closes with keyboard', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.goto('/');

    // Find and focus the mobile menu toggle
    const menuToggle = page.locator('#mobile-menu-toggle');
    await menuToggle.focus();
    await page.keyboard.press('Enter');

    // Menu should open
    await expect(page.locator('#mobile-menu')).toBeVisible();

    // Escape should close the menu
    await page.keyboard.press('Escape');

    // Menu should be closed (translated out of view)
    const menuTransform = await page.locator('#mobile-menu').evaluate((el) =>
      window.getComputedStyle(el).transform
    );
    // After escape, menu should have translate-x-full applied
    const ariaExpanded = await menuToggle.getAttribute('aria-expanded');
    expect(ariaExpanded).toBe('false');
  });

  test('FAQ accordion is keyboard operable', async ({ page }) => {
    await page.goto('/');

    const firstFaqButton = page.locator('.faq-question').first();
    await firstFaqButton.focus();
    await page.keyboard.press('Enter');

    const isExpanded = await firstFaqButton.getAttribute('aria-expanded');
    expect(isExpanded).toBe('true');
  });

  test('Portfolio modal can be closed with Escape key', async ({ page }) => {
    await page.goto('/portfolio');

    // Open first project modal
    const firstProjectCard = page.locator('.project-card').first();
    if (await firstProjectCard.count() > 0) {
      await firstProjectCard.click();

      const modal = page.locator('[role="dialog"]');
      await expect(modal).toBeVisible();

      await page.keyboard.press('Escape');
      await expect(modal).not.toBeVisible();
    }
  });

  test('All interactive elements are reachable via tab on Contact page', async ({ page }) => {
    await page.goto('/contact');

    const interactiveElements = await page.locator('a, button, input, select, textarea').all();
    const visibleInteractive = [];

    for (const el of interactiveElements) {
      if (await el.isVisible()) {
        const tabindex = await el.getAttribute('tabindex');
        if (tabindex !== '-1') {
          visibleInteractive.push(el);
        }
      }
    }

    expect(visibleInteractive.length).toBeGreaterThan(0);
  });
});

// ============================================================
// WCAG 2.1 AA - 2.4.3 Focus Order
// ============================================================
test.describe('2.4.3 Focus Order', () => {
  test('Focus order follows logical reading order on Home page', async ({ page }) => {
    await page.goto('/');

    const tabOrder: string[] = [];
    let maxTabs = 20; // Limit to first 20 tab stops

    await page.keyboard.press('Tab'); // skip link

    while (maxTabs-- > 0) {
      const focused = await page.evaluate(() => {
        const el = document.activeElement as HTMLElement;
        return {
          tag: el?.tagName,
          text: el?.textContent?.trim()?.substring(0, 30),
          href: el?.getAttribute('href'),
        };
      });
      tabOrder.push(`${focused.tag}:${focused.text || focused.href}`);
      await page.keyboard.press('Tab');
    }

    // Skip link should appear first
    expect(tabOrder[0]).toContain('#main-content');
  });
});

// ============================================================
// WCAG 2.1 AA - 2.4.6 Headings and Labels
// ============================================================
test.describe('2.4.6 Headings and Labels', () => {
  for (const page of pages) {
    test(`${page.name}: heading hierarchy is logical (no skipped levels)`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const headings = await browserPage.locator('h1, h2, h3, h4, h5, h6').all();
      const levels: number[] = [];

      for (const heading of headings) {
        const tag = await heading.evaluate((el) => el.tagName);
        levels.push(parseInt(tag.replace('H', '')));
      }

      // Check that heading levels don't skip (e.g., h1 -> h3 without h2)
      for (let i = 1; i < levels.length; i++) {
        const jump = levels[i] - levels[i - 1];
        // Allow going down by any amount or going up, but not jumping more than 1 level
        expect(jump, `Heading level skips from h${levels[i-1]} to h${levels[i]} on ${page.path}`).toBeLessThanOrEqual(1);
      }
    });

    test(`${page.name}: all form controls have visible labels`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const selects = await browserPage.locator('select').all();
      for (const select of selects) {
        const id = await select.getAttribute('id');
        const ariaLabel = await select.getAttribute('aria-label');
        let hasLabel = ariaLabel !== null;

        if (id && !hasLabel) {
          const labelCount = await browserPage.locator(`label[for="${id}"]`).count();
          hasLabel = labelCount > 0;
        }

        expect(hasLabel, `Select ${id || 'unknown'} on ${page.path} missing label`).toBeTruthy();
      }
    });
  }
});

// ============================================================
// WCAG 2.1 AA - 2.4.7 Focus Visible
// ============================================================
test.describe('2.4.7 Focus Visible', () => {
  test('Navigation links have visible focus indicators', async ({ page }) => {
    await page.goto('/');

    const navLink = page.locator('nav a').first();
    await navLink.focus();

    const outlineStyle = await navLink.evaluate((el) => {
      const style = window.getComputedStyle(el);
      return {
        outline: style.outline,
        outlineWidth: style.outlineWidth,
        boxShadow: style.boxShadow,
      };
    });

    const hasFocusIndicator =
      (outlineStyle.outline !== 'none' && outlineStyle.outline !== '') ||
      (outlineStyle.outlineWidth !== '0px' && outlineStyle.outlineWidth !== '') ||
      (outlineStyle.boxShadow !== 'none' && outlineStyle.boxShadow !== '');

    expect(hasFocusIndicator, 'Navigation link must have a visible focus indicator').toBeTruthy();
  });

  test('Form inputs have visible focus indicators', async ({ page }) => {
    await page.goto('/contact');

    const input = page.locator('#name');
    await input.focus();

    const focusStyle = await input.evaluate((el) => {
      const style = window.getComputedStyle(el);
      return {
        outline: style.outline,
        boxShadow: style.boxShadow,
        borderColor: style.borderColor,
      };
    });

    const hasFocusIndicator =
      (focusStyle.outline !== 'none' && focusStyle.outline !== '') ||
      (focusStyle.boxShadow !== 'none' && focusStyle.boxShadow !== '');

    expect(hasFocusIndicator, 'Form input must have a visible focus indicator').toBeTruthy();
  });

  test('Buttons have visible focus indicators', async ({ page }) => {
    await page.goto('/contact');

    const button = page.locator('button[type="submit"]').first();
    await button.focus();

    const focusStyle = await button.evaluate((el) => {
      const style = window.getComputedStyle(el);
      return {
        outline: style.outline,
        boxShadow: style.boxShadow,
      };
    });

    const hasFocusIndicator =
      (focusStyle.outline !== 'none' && focusStyle.outline !== '') ||
      (focusStyle.boxShadow !== 'none' && focusStyle.boxShadow !== '');

    expect(hasFocusIndicator, 'Submit button must have a visible focus indicator').toBeTruthy();
  });
});

// ============================================================
// WCAG 2.1 AA - 3.3.1 Error Identification
// ============================================================
test.describe('3.3.1 Error Identification', () => {
  test('Contact form shows error messages for invalid submissions', async ({ page }) => {
    await page.goto('/contact');

    // Submit empty form
    await page.locator('button[type="submit"]').click();

    // Error messages should appear
    const nameError = page.locator('#name-error, [data-error="name"]');
    await expect(nameError).toBeVisible();
  });

  test('Contact form error messages are associated with inputs via aria-describedby', async ({ page }) => {
    await page.goto('/contact');

    const nameInput = page.locator('#name');
    const describedBy = await nameInput.getAttribute('aria-describedby');
    expect(describedBy).toBeTruthy();

    if (describedBy) {
      const errorEl = page.locator(`#${describedBy}`);
      const count = await errorEl.count();
      expect(count).toBeGreaterThan(0);
    }
  });

  test('Newsletter form has accessible error/success messaging', async ({ page }) => {
    await page.goto('/');

    const newsletterMessage = page.locator('#newsletter-message');
    const ariaLive = await newsletterMessage.getAttribute('aria-live');
    expect(ariaLive).toBeTruthy();
  });
});

// ============================================================
// WCAG 2.1 AA - 3.3.2 Labels or Instructions
// ============================================================
test.describe('3.3.2 Labels or Instructions', () => {
  test('Contact form has required field indicators', async ({ page }) => {
    await page.goto('/contact');

    // Name field should be marked as required
    const nameInput = page.locator('#name');
    const required = await nameInput.getAttribute('required');
    const ariaRequired = await nameInput.getAttribute('aria-required');
    expect(required !== null || ariaRequired === 'true').toBeTruthy();
  });

  test('Newsletter email input has a label', async ({ page }) => {
    await page.goto('/');

    const emailInput = page.locator('#newsletter-email');
    const id = await emailInput.getAttribute('id');
    const ariaLabel = await emailInput.getAttribute('aria-label');
    let hasLabel = ariaLabel !== null;

    if (id && !hasLabel) {
      const labelCount = await page.locator(`label[for="${id}"]`).count();
      hasLabel = labelCount > 0;
    }

    expect(hasLabel, 'Newsletter email input must have a label').toBeTruthy();
  });
});

// ============================================================
// WCAG 2.1 AA - 4.1.2 Name, Role, Value (Interactive Components)
// ============================================================
test.describe('4.1.2 Name, Role, Value', () => {
  for (const page of pages) {
    test(`${page.name}: all buttons have accessible names`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const buttons = await browserPage.locator('button').all();
      for (const button of buttons) {
        const text = await button.textContent();
        const ariaLabel = await button.getAttribute('aria-label');
        const ariaLabelledby = await button.getAttribute('aria-labelledby');
        const title = await button.getAttribute('title');

        const hasAccessibleName =
          (text && text.trim().length > 0) ||
          ariaLabel !== null ||
          ariaLabelledby !== null ||
          title !== null;

        expect(hasAccessibleName, `Button without accessible name on ${page.path}`).toBeTruthy();
      }
    });

    test(`${page.name}: all links have accessible names`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const links = await browserPage.locator('a').all();
      for (const link of links) {
        const text = await link.textContent();
        const ariaLabel = await link.getAttribute('aria-label');
        const title = await link.getAttribute('title');
        const hasImg = (await link.locator('img[alt]').count()) > 0;
        const hasSvgWithLabel = (await link.locator('svg[aria-label]').count()) > 0;
        // SVG links with accompanying text (the SVG is aria-hidden) are accessible
        const hasSvgWithText = (await link.locator('svg[aria-hidden]').count()) > 0 && text && text.trim().length > 0;

        const hasAccessibleName =
          (text && text.trim().length > 0) ||
          ariaLabel !== null ||
          title !== null ||
          hasImg ||
          hasSvgWithLabel ||
          hasSvgWithText;

        expect(hasAccessibleName, `Link without accessible name on ${page.path}: ${await link.getAttribute('href')}`).toBeTruthy();
      }
    });
  }

  test('Mobile menu toggle has correct aria-expanded state', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.goto('/');

    const toggle = page.locator('#mobile-menu-toggle');

    // Initially closed
    expect(await toggle.getAttribute('aria-expanded')).toBe('false');

    // Open menu
    await toggle.click();
    expect(await toggle.getAttribute('aria-expanded')).toBe('true');

    // Close menu
    await toggle.click();
    expect(await toggle.getAttribute('aria-expanded')).toBe('false');
  });

  test('Portfolio filter tabs have correct aria-selected state', async ({ page }) => {
    await page.goto('/portfolio');

    const tablist = page.locator('[role="tablist"]');
    await expect(tablist).toBeVisible();

    const tabs = page.locator('[role="tab"]');
    const tabCount = await tabs.count();
    expect(tabCount).toBeGreaterThan(0);

    // First tab should be selected by default
    const firstTab = tabs.first();
    expect(await firstTab.getAttribute('aria-selected')).toBe('true');
  });

  test('FAQ accordion buttons have aria-expanded and aria-controls', async ({ page }) => {
    await page.goto('/');

    const faqButtons = page.locator('.faq-question');
    const count = await faqButtons.count();
    expect(count).toBeGreaterThan(0);

    for (let i = 0; i < count; i++) {
      const btn = faqButtons.nth(i);
      const ariaExpanded = await btn.getAttribute('aria-expanded');
      const ariaControls = await btn.getAttribute('aria-controls');

      expect(ariaExpanded, `FAQ button ${i} missing aria-expanded`).not.toBeNull();
      expect(ariaControls, `FAQ button ${i} missing aria-controls`).not.toBeNull();

      // Verify the controlled element exists
      if (ariaControls) {
        const controlled = page.locator(`#${ariaControls}`);
        expect(await controlled.count(), `FAQ answer ${ariaControls} not found`).toBeGreaterThan(0);
      }
    }
  });

  test('Testimonials carousel has aria-roledescription', async ({ page }) => {
    await page.goto('/');

    const carousel = page.locator('[aria-roledescription="carousel"]');
    await expect(carousel).toBeAttached();
  });

  test('Mobile menu dialog has correct ARIA attributes', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.goto('/');

    const mobileMenu = page.locator('#mobile-menu');
    const role = await mobileMenu.getAttribute('role');
    const ariaModal = await mobileMenu.getAttribute('aria-modal');
    const ariaLabel = await mobileMenu.getAttribute('aria-label');

    expect(role).toBe('dialog');
    expect(ariaModal).toBe('true');
    expect(ariaLabel).toBeTruthy();
  });
});

// ============================================================
// WCAG 2.1 AA - 4.1.3 Status Messages
// ============================================================
test.describe('4.1.3 Status Messages', () => {
  test('Toast container has aria-live region', async ({ page }) => {
    await page.goto('/contact');

    const toastContainer = page.locator('#toast-container');
    const ariaLive = await toastContainer.getAttribute('aria-live');
    expect(ariaLive).toBeTruthy();
  });

  test('Newsletter message has aria-live region', async ({ page }) => {
    await page.goto('/');

    const messageEl = page.locator('#newsletter-message');
    const ariaLive = await messageEl.getAttribute('aria-live');
    expect(ariaLive).not.toBeNull();
  });
});

// ============================================================
// Additional: Skip Navigation
// ============================================================
test.describe('Skip Navigation', () => {
  test('Skip link is present in the DOM', async ({ page }) => {
    await page.goto('/');

    const skipLink = page.locator('a[href="#main-content"]');
    await expect(skipLink).toBeAttached();
  });

  test('Main content landmark has id="main-content"', async ({ page }) => {
    await page.goto('/');

    const main = page.locator('#main-content');
    await expect(main).toBeAttached();
    const tag = await main.evaluate((el) => el.tagName.toLowerCase());
    expect(tag).toBe('main');
  });

  test('Skip link becomes visible on focus', async ({ page }) => {
    await page.goto('/');

    const skipLink = page.locator('a[href="#main-content"]');
    await skipLink.focus();

    // After focus, the link should be visible (not sr-only)
    const isVisible = await skipLink.isVisible();
    expect(isVisible).toBeTruthy();
  });
});

// ============================================================
// Additional: Reduced Motion
// ============================================================
test.describe('Reduced Motion', () => {
  test('Animations respect prefers-reduced-motion media query', async ({ page }) => {
    // Enable prefers-reduced-motion
    await page.emulateMedia({ reducedMotion: 'reduce' });
    await page.goto('/');

    // Page should still load and be functional
    await expect(page.locator('h1').first()).toBeVisible();
    await expect(page.locator('nav')).toBeVisible();
  });
});

// ============================================================
// Additional: Page Language
// ============================================================
test.describe('Language Attribute', () => {
  for (const page of pages) {
    test(`${page.name}: html element has lang attribute`, async ({ page: browserPage }) => {
      await browserPage.goto(page.path);

      const lang = await browserPage.locator('html').getAttribute('lang');
      expect(lang).toBeTruthy();
      expect(lang).toMatch(/^[a-z]{2}/); // Valid ISO language code
    });
  }
});
