import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { TauriBarChart, TauriLineChart, type ChartPoint } from './TauriCharts';

const zero: ChartPoint[] = Array.from({ length: 7 }, (_, i) => ({ label: `روز ${i}`, value: 0 }));
const real: ChartPoint[] = [
  { label: '۷ خرداد', value: 15 },
  { label: '۸ خرداد', value: 40 },
  { label: '۹ خرداد', value: 48 },
];

const EMPTY = 'داده‌ای برای نمایش نیست';

/**
 * صفحه RTL است، پس در یک ردیفِ flex «اولین فرزند» سمت راست رندر می‌شود. ستون مقادیر
 * باید سمت چپ بنشیند، یعنی باید فرزند **آخر** باشد و فاصلهٔ ردیف برچسب‌ها هم از چپ
 * گرفته شود. جابه‌جا شدن این ترتیب، اعداد را بی‌صدا به سمت راست برمی‌گرداند.
 */
describe('ChartFrame — جای محور مقادیر', () => {
  const frameOf = (container: HTMLElement) => {
    const root = container.firstElementChild as HTMLElement;
    return {
      plotRow: root.firstElementChild as HTMLElement,
      labelRow: root.lastElementChild as HTMLElement,
    };
  };

  it('ستون مقادیر آخرین فرزند ردیف است تا در RTL سمت چپ بیفتد', () => {
    const { container } = render(<TauriLineChart data={real} />);
    const { plotRow } = frameOf(container);

    const ticks = plotRow.lastElementChild as HTMLElement;
    expect(ticks.textContent).toContain('۰');
    expect(ticks.className).toContain('flex-col');
    // نمودار خودش فرزند اول است، پس سمت راست می‌ماند.
    expect((plotRow.firstElementChild as HTMLElement).className).toContain('overflow-hidden');
  });

  it('ردیف برچسب‌ها به اندازهٔ ستون مقادیر از چپ فاصله می‌گیرد', () => {
    const { container } = render(<TauriLineChart data={real} />);
    const { labelRow } = frameOf(container);

    expect(labelRow.style.paddingLeft).not.toBe('');
    expect(labelRow.style.paddingRight).toBe('');
  });
});

describe('TauriBarChart', () => {
  it('shows the empty state when every value is zero', () => {
    render(<TauriBarChart data={zero} />);
    expect(screen.getByText(EMPTY)).toBeInTheDocument();
  });

  it('shows the empty state when there is no data', () => {
    render(<TauriBarChart data={[]} />);
    expect(screen.getByText(EMPTY)).toBeInTheDocument();
  });

  it('renders the chart (x labels, no empty state) with real data', () => {
    render(<TauriBarChart data={real} />);
    expect(screen.queryByText(EMPTY)).not.toBeInTheDocument();
    expect(screen.getByText('۸ خرداد')).toBeInTheDocument();
  });

  it('renders one bar per day of a full Jalali month', () => {
    const month: ChartPoint[] = Array.from({ length: 31 }, (_, i) => ({ label: String(i + 1), value: i % 5 }));
    const { container } = render(<TauriBarChart data={month} />);
    expect(container.querySelectorAll('[class*="bg-[var(--primary)]"]')).toHaveLength(31);
  });

  /**
   * A fixed-width bar makes its column's intrinsic min-width 24px, so a 31-day
   * month sums past the plot width and the row overflows out of the card
   * (bars land on the neighbouring chart). Columns must be shrinkable and the
   * bar fluid-but-capped.
   */
  it('keeps a dense month inside the card: shrinkable columns, fluid bars', () => {
    const month: ChartPoint[] = Array.from({ length: 31 }, (_, i) => ({ label: String(i + 1), value: i % 5 }));
    const { container } = render(<TauriBarChart data={month} />);

    const bar = container.querySelector('[class*="bg-[var(--primary)]"]') as HTMLElement;
    // کسری از ستون، نه تمام آن — وگرنه میله‌ها بدون فاصله به هم می‌چسبند
    expect(bar.className).toMatch(/\bw-\[\d+%\]/);
    expect(bar.className.split(/\s+/)).not.toContain('w-full');
    expect(bar.className).toContain('max-w-[24px]');
    // فقط سقف عرض، نه عرض ثابت (max-w-[24px] خودش شامل رشته‌ی w-[24px] است)
    expect(bar.className.split(/\s+/)).not.toContain('w-[24px]');

    const column = bar.parentElement as HTMLElement;
    expect(column.className).toContain('min-w-0');

    const plot = container.querySelector('.relative.flex-1') as HTMLElement;
    expect(plot.className).toContain('overflow-hidden');
  });

  /**
   * styles.css ships an unlayered legacy `.flex { align-items: center }` that
   * beats Tailwind's `items-*` utilities and collapses the plot column to zero
   * height. The frame must pin alignment inline, or the chart renders blank.
   */
  it('pins flex alignment inline so the legacy global .flex cannot collapse it', () => {
    const { container } = render(<TauriBarChart data={real} />);
    const frame = container.querySelector('.h-\\[300px\\]') as HTMLElement;
    expect(frame.style.alignItems).toBe('stretch');
    expect((frame.firstElementChild as HTMLElement).style.alignItems).toBe('stretch');
  });
});

describe('TauriLineChart', () => {
  it('shows the empty state when every value is zero', () => {
    render(<TauriLineChart data={zero} />);
    expect(screen.getByText(EMPTY)).toBeInTheDocument();
  });

  it('shows the empty state with fewer than two points', () => {
    render(<TauriLineChart data={[{ label: 'x', value: 5 }]} />);
    expect(screen.getByText(EMPTY)).toBeInTheDocument();
  });

  it('renders the chart with real data', () => {
    const { container } = render(<TauriLineChart data={real} />);
    expect(screen.queryByText(EMPTY)).not.toBeInTheDocument();
    expect(container.querySelector('svg path')).toBeTruthy();
  });

  /** سبک «gradient line»: خط از گرادیان افقی رنگ می‌گیرد، نه یک رنگ ثابت. */
  it('paints the line with the horizontal gradient and a glow', () => {
    const { container } = render(<TauriLineChart data={real} />);
    const line = container.querySelectorAll('svg path')[1] as SVGPathElement;

    expect(line.getAttribute('stroke')).toBe('url(#tdIncomeStroke)');
    expect(line.getAttribute('filter')).toBe('url(#tdIncomeGlow)');
    expect(container.querySelector('#tdIncomeStroke')).toBeTruthy();
    expect(container.querySelector('#tdIncomeGlow feDropShadow')).toBeTruthy();
  });

  /** نقطه و تولتیپ برای هر داده ساخته می‌شود و با هاور نمایان می‌شود. */
  it('renders a hover marker and value tooltip per point', () => {
    const { container } = render(<TauriLineChart data={real} />);

    expect(container.querySelectorAll('.td-hit')).toHaveLength(real.length);
    expect(container.querySelectorAll('.td-dot')).toHaveLength(real.length);
    // مقدارها با ارقام فارسی در تولتیپ
    expect(screen.getByText('۴۸')).toBeInTheDocument();
  });
});

/**
 * ماه‌های نیامده صفر برمی‌گردند؛ رسم‌کردنشان یک خط صاف تا انتهای سال می‌ساخت.
 * از آخرین دادهٔ واقعی به بعد باید روند نقطه‌چین ادامه پیدا کند.
 */
describe('TauriLineChart — ادامهٔ پیش‌بینی', () => {
  /** ۴ ماه واقعیِ صعودی + ۸ ماه نیامده (صفر) */
  const year: ChartPoint[] = Array.from({ length: 12 }, (_, i) => ({
    label: `ماه ${i + 1}`,
    value: i < 4 ? (i + 1) * 10 : 0,
  }));

  it('بدون actualCount خط پیش‌بینی ندارد (رفتار قبلی حفظ می‌شود)', () => {
    const { container } = render(<TauriLineChart data={year} />);
    expect(container.querySelector('.td-forecast')).toBeNull();
    expect(screen.queryByText('نقطه‌چین: پیش‌بینی')).not.toBeInTheDocument();
  });

  it('با actualCount ماه‌های باقی‌مانده را نقطه‌چین ادامه می‌دهد', () => {
    const { container } = render(<TauriLineChart data={year} actualCount={4} />);

    const forecast = container.querySelector('.td-forecast');
    expect(forecast).toBeTruthy();
    expect(forecast!.getAttribute('stroke')).toBe('var(--accent)');
    expect(screen.getByText('نقطه‌چین: پیش‌بینی')).toBeInTheDocument();
  });

  it('روند صعودی را ادامه می‌دهد و صفرها را رسم نمی‌کند', () => {
    render(<TauriLineChart data={year} actualCount={4} />);
    // شیب ۱۰ در ماه، آخرین واقعی ۴۰ ⇒ ماه پنجم ۵۰
    expect(screen.getByText('پیش‌بینی: ۵۰')).toBeInTheDocument();
    // آخرین ماه: ۴۰ + ۸×۱۰ = ۱۲۰
    expect(screen.getByText('پیش‌بینی: ۱۲۰')).toBeInTheDocument();
  });

  it('پیش‌بینی هرگز منفی نمی‌شود', () => {
    const falling: ChartPoint[] = Array.from({ length: 12 }, (_, i) => ({
      label: `ماه ${i + 1}`,
      value: i < 3 ? 100 - i * 45 : 0,
    }));
    const { container } = render(<TauriLineChart data={falling} actualCount={3} />);

    const tips = Array.from(container.querySelectorAll('.td-tip'))
      .map((el) => el.textContent ?? '')
      .filter((t) => t.startsWith('پیش‌بینی'));
    expect(tips.length).toBeGreaterThan(0);
    expect(tips.some((t) => t.includes('-') || t.includes('−'))).toBe(false);
  });

  it('سال کامل (actualCount برابر طول داده) پیش‌بینی ندارد', () => {
    const full: ChartPoint[] = Array.from({ length: 12 }, (_, i) => ({ label: `ماه ${i + 1}`, value: 10 + i }));
    const { container } = render(<TauriLineChart data={full} actualCount={12} />);
    expect(container.querySelector('.td-forecast')).toBeNull();
  });
});

/**
 * محور زمان راست‌به‌چپ است: برچسب‌ها در جریان RTL چیده می‌شوند، پس فروردین سمت راست
 * می‌نشیند. SVG جریان RTL ندارد و اگر اندیس ۰ به `x=0` برود، خط نسبت به برچسب‌هایش
 * آینه می‌شود — دقیقاً باگی که دادهٔ واقعیِ ابتدای سال را روی ماه‌های پایانی می‌انداخت.
 */
describe('TauriLineChart — جهت محور زمان', () => {
  const year: ChartPoint[] = Array.from({ length: 12 }, (_, i) => ({
    label: `ماه ${i + 1}`,
    value: i < 4 ? (i + 1) * 10 : 0,
  }));

  /** ستونِ هاورِ هر نقطه دقیقاً حول همان نقطه می‌نشیند، پس `left`ش جای نقطه را می‌گوید. */
  const hitCenters = (container: HTMLElement): number[] =>
    Array.from(container.querySelectorAll<HTMLElement>('.td-hit')).map((el) => {
      const left  = parseFloat(el.style.left);
      const width = parseFloat(el.style.width);
      return left + width / 2;
    });

  it('اولین نقطه سمت راست و آخرین نقطه سمت چپ می‌نشیند', () => {
    const { container } = render(<TauriLineChart data={year} actualCount={4} />);
    const centers = hitCenters(container);

    expect(centers[0]).toBeCloseTo(100, 5);
    expect(centers[centers.length - 1]).toBeCloseTo(0, 5);
  });

  it('نقطه‌ها به ترتیب زمان از راست به چپ پیش می‌روند', () => {
    const { container } = render(<TauriLineChart data={year} actualCount={4} />);
    const centers = hitCenters(container);

    for (let i = 1; i < centers.length; i++) {
      expect(centers[i]).toBeLessThan(centers[i - 1]);
    }
  });

  /** بخش واقعی سمت راست است و پیش‌بینی سمت چپ، نه برعکس. */
  it('بخش واقعی راست‌تر از بخش پیش‌بینی است', () => {
    const { container } = render(<TauriLineChart data={year} actualCount={4} />);
    const centers = hitCenters(container);

    const lastActual  = centers[3];
    const firstForecast = centers[4];
    expect(lastActual).toBeGreaterThan(firstForecast);
  });

  /** نقطه باید روی خط بنشیند، نه داخل ستونِ خودش سُر بخورد. */
  it('نقطه در مرکز ستون هاورِ خودش است', () => {
    const { container } = render(<TauriLineChart data={year} actualCount={4} />);

    for (const dot of Array.from(container.querySelectorAll<HTMLElement>('.td-dot'))) {
      expect(dot.style.left).toBe('50%');
    }
  });
});
