import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { SkillDetail } from '../SkillDetail';
import { renderWithRouter } from '../../utils/testUtils';
import { createMockSkill } from '../../factories/skill';
import { useSkills } from '../../context/SkillContext';

// Mock the SkillStarButton component
vi.mock('../../components/SkillStarButton', () => ({
  SkillStarButton: ({ skillId, communityCount }: { skillId: string; communityCount?: number }) => (
    <button data-testid="star-button" data-skill-id={skillId} data-community-count={communityCount}>
      Save locally
    </button>
  ),
}));

// Mock useSkills hook
vi.mock('../../context/SkillContext', async (importOriginal) => {
  const actual = await importOriginal<any>();
  return {
    ...actual,
    useSkills: vi.fn(),
  };
});

// Mock react-markdown to avoid lazy loading issues in tests
vi.mock('react-markdown', () => ({
  default: ({ children }: { children: string }) => <div data-testid="markdown-content">{children}</div>,
}));

describe('SkillDetail', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    localStorage.clear();
    window.history.pushState({}, '', '/');
  });

  describe('Loading state', () => {
    it('should show loading spinner when context is loading', async () => {
      (useSkills as Mock).mockReturnValue({
        skills: [],
        stars: {},
        loading: true,
      });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/test-skill',
        path: '/skill/:id',
        useProvider: false
      });

      expect(screen.getByTestId('loader')).toBeInTheDocument();
    });

    it('should show loading spinner when markdown is loading', async () => {
      const mockSkill = createMockSkill({ id: 'test-skill' });
      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: {},
        loading: false,
      });

      // Mock fetch for markdown content to never resolve
      global.fetch = vi.fn().mockReturnValue(new Promise(() => { }));

      renderWithRouter(<SkillDetail />, {
        route: '/skill/test-skill',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        expect(screen.getByTestId('loader')).toBeInTheDocument();
      });
    });
  });

  describe('Skill rendering', () => {
    it('should render skill details correctly', async () => {
      const mockSkill = createMockSkill({
        id: 'react-patterns',
        name: 'react-patterns',
        description: 'React design patterns and best practices',
        category: 'frontend',
      });

      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: { 'react-patterns': 5 },
        loading: false,
      });

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        text: async () => '# React Patterns\n\nThis is the skill content.',
      });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/react-patterns',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        expect(screen.getByText('@react-patterns')).toBeInTheDocument();
        expect(screen.getByText('React design patterns and best practices')).toBeInTheDocument();
        expect(screen.getByText(/Related topic guides/i)).toBeInTheDocument();
        expect(screen.getByRole('link', { name: /Antigravity plugins/i })).toHaveAttribute(
          'href',
          '/topics/antigravity-plugins',
        );
        expect(screen.getByTestId('markdown-content')).toHaveTextContent('This is the skill content.');
        expect(document.title).toContain('react-patterns');
        expect(document.querySelector('meta[name="twitter:title"]')).toHaveAttribute(
          'content',
          '@react-patterns | Agentic Awesome Skills',
        );
        expect(global.fetch).toHaveBeenCalled();
      }, { timeout: 3000 });
    });

    it('falls back to the next markdown candidate when the first response is html', async () => {
      const mockSkill = createMockSkill({
        id: 'fallback-skill',
        name: 'fallback-skill',
      });

      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: {},
        loading: false,
      });

      window.history.pushState({}, '', '/skill/fallback-skill');

      global.fetch = vi.fn()
        .mockResolvedValueOnce({
          ok: true,
          text: async () => '<!doctype html><html></html>',
        })
        .mockResolvedValueOnce({
          ok: true,
          text: async () => '# Loaded from fallback',
        });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/fallback-skill',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        expect(screen.getByTestId('markdown-content')).toHaveTextContent('Loaded from fallback');
      });
    });

    it('shows a retry action when markdown loading fails', async () => {
      const mockSkill = createMockSkill({
        id: 'broken-skill',
        name: 'broken-skill',
      });

      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: {},
        loading: false,
      });

      window.history.pushState({}, '', '/skill/broken-skill');

      global.fetch = vi.fn()
        .mockResolvedValueOnce({
          ok: false,
          status: 404,
          text: async () => '',
        })
        .mockResolvedValueOnce({
          ok: false,
          status: 404,
          text: async () => '',
        })
        .mockResolvedValueOnce({
          ok: false,
          status: 404,
          text: async () => '',
        })
        .mockResolvedValueOnce({
          ok: false,
          status: 404,
          text: async () => '',
        })
        .mockResolvedValueOnce({
          ok: false,
          status: 404,
          text: async () => '',
        })
        .mockResolvedValueOnce({
          ok: true,
          text: async () => '# Recovered content',
        });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/broken-skill',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        expect(screen.getByText(/Failed to Load Content/i)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByRole('button', { name: /Retry loading content/i }));

      await waitFor(() => {
        expect(screen.getByTestId('markdown-content')).toHaveTextContent('Recovered content');
      });
    });

    it('should show skill not found when id does not exist', async () => {
      (useSkills as Mock).mockReturnValue({
        skills: [],
        stars: {},
        loading: false,
      });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/nonexistent',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        expect(screen.getByText(/Error Loading Skill/i)).toBeInTheDocument();
        expect(screen.getByText(/Skill not found in registry/i)).toBeInTheDocument();
        expect(document.title).toContain('nonexistent');
      });
    });
  });

  describe('Copy functionality', () => {
    it('should copy skill name to clipboard when clicked', async () => {
      const mockSkill = createMockSkill({ id: 'click-test', name: 'click-test' });

      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: {},
        loading: false,
      });

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        text: async () => 'Content',
      });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/click-test',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /Copy @Skill/i })).toBeInTheDocument();
      });

      const copyButton = screen.getByRole('button', { name: /Copy @Skill/i });
      fireEvent.click(copyButton);

      expect(navigator.clipboard.writeText).toHaveBeenCalledWith('Use @click-test');
    });

    it('should send the exact skill to the Workbench', async () => {
      const mockSkill = createMockSkill({ id: 'click-install', name: 'click-install' });

      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: {},
        loading: false,
      });

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        text: async () => 'Content',
      });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/click-install',
        path: '/skill/:id',
        useProvider: false,
      });

      await waitFor(() => {
        expect(screen.getByRole('link', { name: /Compose exact install/i })).toBeInTheDocument();
      });

      expect(screen.getByRole('link', { name: /Compose exact install/i })).toHaveAttribute(
        'href',
        '/workbench?selected=click-install&host=codex',
      );
    });
  });

  describe('Star button integration', () => {
    it('should render star button component with correct count', async () => {
      const mockSkill = createMockSkill({ id: 'star-integration' });

      (useSkills as Mock).mockReturnValue({
        skills: [mockSkill],
        stars: { 'star-integration': 10 },
        loading: false,
      });

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        text: async () => 'Content',
      });

      renderWithRouter(<SkillDetail />, {
        route: '/skill/star-integration',
        path: '/skill/:id',
        useProvider: false
      });

      await waitFor(() => {
        const starBtn = screen.getByTestId('star-button');
        expect(starBtn).toBeInTheDocument();
        expect(starBtn).toHaveAttribute('data-community-count', '10');
      });
    });
  });
});
