import React from 'react';
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { act, render, screen, fireEvent } from '@testing-library/react';
import { Panel } from '../../../src/renderer/components/Panel/Panel';
import { useAppStore } from '../../../src/renderer/store';
import type { PostData, MediaData } from '../../../src/main/shared/electronApi';

const createPost = (overrides: Partial<PostData> = {}): PostData => ({
  id: 'post-1',
  projectId: 'project-1',
  title: 'First Post',
  slug: 'first-post',
  content: 'Hello',
  status: 'draft',
  createdAt: '2026-02-01T08:00:00.000Z',
  updatedAt: '2026-02-01T08:00:00.000Z',
  tags: [],
  categories: ['article'],
  ...overrides,
});

const createMedia = (overrides: Partial<MediaData> = {}): MediaData => ({
  id: 'media-1',
  projectId: 'project-1',
  filename: 'image-1.jpg',
  originalName: 'image-1.jpg',
  mimeType: 'image/jpeg',
  size: 123,
  createdAt: '2026-02-01T08:00:00.000Z',
  updatedAt: '2026-02-01T08:00:00.000Z',
  tags: [],
  ...overrides,
});

describe('Panel', () => {
  beforeEach(() => {
    vi.clearAllMocks();

    (window as any).electronAPI = {
      ...(window as any).electronAPI,
      git: {
        ...(window as any).electronAPI?.git,
        getFileHistory: vi.fn().mockResolvedValue([]),
      },
      media: {
        ...(window as any).electronAPI?.media,
        getFilePath: vi.fn().mockResolvedValue('/repo/path/media/2026/02/image-1.jpg'),
      },
      posts: {
        ...(window as any).electronAPI?.posts,
        get: vi.fn().mockResolvedValue(null),
        getLinksTo: vi.fn().mockResolvedValue([]),
        getLinkedBy: vi.fn().mockResolvedValue([]),
      },
    };

    useAppStore.setState({
      panelVisible: true,
      panelActiveTab: 'tasks',
      tasks: [],
      activeProject: {
        id: 'project-1',
        name: 'Test Project',
        slug: 'test-project',
        isActive: true,
        dataPath: '/repo/path',
        createdAt: '2026-02-01T08:00:00.000Z',
        updatedAt: '2026-02-01T08:00:00.000Z',
      },
      posts: [createPost()],
      media: [createMedia()],
      tabs: [{ type: 'post', id: 'post-1', isTransient: false }],
      activeTabId: 'post-1',
    });
  });

  afterEach(() => {
    useAppStore.setState({ panelVisible: false, panelActiveTab: 'tasks' });
  });

  it('renders a Git Log tab label instead of Sync Log', () => {
    render(<Panel />);

    expect(screen.getByRole('tab', { name: 'Git Log' })).toBeInTheDocument();
    expect(screen.queryByText('Sync Log')).not.toBeInTheDocument();
  });

  it('shows Post Links tab when active editor is a post', () => {
    render(<Panel />);

    expect(screen.getByRole('tab', { name: 'Post Links' })).toBeInTheDocument();
  });

  it('hides Post Links tab when active editor is not a post', () => {
    useAppStore.setState({
      tabs: [{ type: 'media', id: 'media-1', isTransient: false }],
      activeTabId: 'media-1',
    });

    render(<Panel />);

    expect(screen.queryByRole('tab', { name: 'Post Links' })).not.toBeInTheDocument();
  });

  it('lists from/to post slugs for links related to active post', async () => {
    (window as any).electronAPI.posts.getLinkedBy = vi.fn().mockResolvedValue([
      createPost({ id: 'post-2', slug: 'source-post', title: 'Source Post' }),
    ]);
    (window as any).electronAPI.posts.getLinksTo = vi.fn().mockResolvedValue([
      createPost({ id: 'post-3', slug: 'target-post', title: 'Target Post' }),
    ]);

    render(<Panel />);

    fireEvent.click(screen.getByRole('tab', { name: 'Post Links' }));

    await vi.waitFor(() => {
      expect((window as any).electronAPI.posts.getLinkedBy).toHaveBeenCalledWith('post-1');
      expect((window as any).electronAPI.posts.getLinksTo).toHaveBeenCalledWith('post-1');
    });

    expect(await screen.findByText('from source-post')).toBeInTheDocument();
    expect(screen.getByText('to target-post')).toBeInTheDocument();
  });

  it('opens related post tab when clicking a post link row', async () => {
    (window as any).electronAPI.posts.getLinkedBy = vi.fn().mockResolvedValue([
      createPost({ id: 'post-2', slug: 'source-post', title: 'Source Post' }),
    ]);
    (window as any).electronAPI.posts.getLinksTo = vi.fn().mockResolvedValue([]);

    render(<Panel />);

    fireEvent.click(screen.getByRole('tab', { name: 'Post Links' }));

    const fromButton = await screen.findByRole('button', { name: 'from source-post' });
    fireEvent.click(fromButton);

    expect(useAppStore.getState().tabs).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ type: 'post', id: 'post-2', isTransient: false }),
      ])
    );
    expect(useAppStore.getState().activeTabId).toBe('post-2');
  });

  it('loads git history for the focused item and updates when active editor changes', async () => {
    const getFileHistory = vi.fn()
      .mockResolvedValueOnce([
        {
          hash: 'abc123def456',
          shortHash: 'abc123d',
          date: '2026-02-16T10:00:00.000Z',
          subject: 'docs: update first post',
          author: 'Dev One',
        },
      ])
      .mockResolvedValueOnce([
        {
          hash: 'def456abc123',
          shortHash: 'def456a',
          date: '2026-02-17T09:00:00.000Z',
          subject: 'chore: replace media file',
          author: 'Dev Two',
        },
      ]);

    (window as any).electronAPI.git.getFileHistory = getFileHistory;

    render(<Panel />);

    fireEvent.click(screen.getByRole('tab', { name: 'Git Log' }));

    await vi.waitFor(() => {
      expect(getFileHistory).toHaveBeenCalledWith('/repo/path', 'posts/2026/02/first-post.md', 50);
    });

    act(() => {
      useAppStore.setState({
        tabs: [{ type: 'media', id: 'media-1', isTransient: false }],
        activeTabId: 'media-1',
      });
    });

    await vi.waitFor(() => {
      expect(getFileHistory).toHaveBeenCalledWith('/repo/path', 'media/2026/02/image-1.jpg', 50);
    });
  });

  it('does not load git history when panel is closed', async () => {
    const getFileHistory = vi.fn().mockResolvedValue([]);
    (window as any).electronAPI.git.getFileHistory = getFileHistory;

    useAppStore.setState({ panelVisible: false, panelActiveTab: 'git-log' });

    render(<Panel />);

    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(getFileHistory).not.toHaveBeenCalled();
  });

  it('does not load git history when Git Log tab is not active', async () => {
    const getFileHistory = vi.fn().mockResolvedValue([]);
    (window as any).electronAPI.git.getFileHistory = getFileHistory;

    useAppStore.setState({ panelActiveTab: 'tasks' });

    render(<Panel />);

    await new Promise((resolve) => setTimeout(resolve, 0));
    expect(getFileHistory).not.toHaveBeenCalled();
  });

  it('disables Git Log tab when focused tab is not a post or media editor', () => {
    useAppStore.setState({
      tabs: [{ type: 'settings', id: 'settings', isTransient: false }],
      activeTabId: 'settings',
    });

    render(<Panel />);

    expect(screen.getByRole('tab', { name: 'Git Log' })).toHaveAttribute('aria-disabled', 'true');
  });

  it('restores the last selected panel tab after remounting the panel', () => {
    const firstRender = render(<Panel />);

    fireEvent.click(screen.getByRole('tab', { name: 'Output' }));
    expect(screen.getByRole('tab', { name: 'Output' })).toHaveAttribute('aria-selected', 'true');

    firstRender.unmount();

    render(<Panel />);

    expect(screen.getByRole('tab', { name: 'Output' })).toHaveAttribute('aria-selected', 'true');
  });

  it('renders output entries when output tab is active', () => {
    useAppStore.setState({
      panelActiveTab: 'output',
      panelOutputEntries: [
        {
          id: 'output-1',
          message: 'hello from script',
          createdAt: '2026-02-22T00:00:00.000Z',
          kind: 'stdout',
        },
      ],
    });

    render(<Panel />);

    expect(screen.getByText('hello from script')).toBeInTheDocument();
  });

  it('copies output entries from output tab with copy button', async () => {
    const writeText = vi.fn().mockResolvedValue(undefined);
    Object.defineProperty(globalThis, 'navigator', {
      value: { clipboard: { writeText } },
      configurable: true,
    });

    useAppStore.setState({
      panelActiveTab: 'output',
      panelOutputEntries: [
        {
          id: 'output-1',
          message: 'pyodide.asm.js missing',
          createdAt: '2026-02-22T00:00:00.000Z',
          kind: 'error',
        },
        {
          id: 'output-2',
          message: 'second line',
          createdAt: '2026-02-22T00:00:01.000Z',
          kind: 'stdout',
        },
      ],
    });

    render(<Panel />);

    fireEvent.click(screen.getByRole('button', { name: 'Copy Output' }));

    await vi.waitFor(() => {
      expect(writeText).toHaveBeenCalledWith('pyodide.asm.js missing\n\nsecond line');
    });
  });

  it('renders grouped tasks as expandable parent rows with child task names in tasks tab', async () => {
    useAppStore.setState({
      tasks: [
        {
          taskId: 'site-render-core-1',
          name: 'Render Site Core',
          status: 'running',
          progress: 42,
          message: 'Generating root pages...',
          startTime: '2026-02-20T10:00:00.000Z',
          groupId: 'site-render-1',
          groupName: 'Render Site',
        },
        {
          taskId: 'site-render-tag-1',
          name: 'Render Tag Archives',
          status: 'pending',
          progress: 0,
          message: 'Waiting to start...',
          startTime: '2026-02-20T10:00:01.000Z',
          groupId: 'site-render-1',
          groupName: 'Render Site',
        },
      ] as any,
    });

    render(<Panel />);

    const parent = screen.getByRole('button', { name: /Render Site \(2, \d+% · 1 running · 1 pending\)/ });
    expect(parent).toBeInTheDocument();
    expect(await screen.findByText('Render Site Core')).toBeInTheDocument();
    expect(screen.getByText('Render Tag Archives')).toBeInTheDocument();
    expect(screen.getByText('42%')).toBeInTheDocument();
  });
});
