/**
 * Testes do exportador Ftool (.txt) — M9.4.
 *
 * Valida a estrutura do arquivo gerado sem depender do browser
 * (serialização pura). Para o modelo, mocka o store Zustand.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';

vi.mock('../store/galpaoStore', () => ({
  useGalpaoStore: {
    getState: () => ({
      width: 15,
      length: 30,
      height: 6,
      roofPitch: 10,
      windAngle: 0,
      wallCpe: { A: -1.1, B: -0.8, C: -0.5, D: -0.5 },
      roofCpe: { E: -1.0, F: -1.0, G: -0.5, H: -0.5, I: 0, J: 0 },
      permeabilityCase: 'four-equally-permeable',
      cpiRatio: 1,
      cpi: -0.3,
    }),
  },
}));

vi.mock('../store/appStore', () => ({
  useWindStore: {
    getState: () => ({
      v0: 40,
      s1: 1,
      s2: 1.0,
      s3: 1.0,
      s3Group: 3,
      terrainCategory: 'II',
      structureClass: 'A',
      vk: 40,
      q: 1.0,
    }),
  },
}));

import {
  buildFtoolModel,
  serializeFtool,
  type FtoolModel,
} from '../export-ftool';

describe('M9.4 — buildFtoolModel (estrutura do modelo)', () => {
  let model: FtoolModel;
  beforeEach(() => {
    model = buildFtoolModel();
  });

  it('Unidades padrão: kN e m', () => {
    expect(model.units.force).toBe('kN');
    expect(model.units.length).toBe('m');
  });

  it('Possui 1 material (Aço)', () => {
    expect(model.materials).toHaveLength(1);
    expect(model.materials[0].name).toBe('Aco');
    expect(model.materials[0].eKpa).toBeGreaterThan(0);
  });

  it('Possui 3 seções (Coluna, TercaE, TercaD)', () => {
    expect(model.sections).toHaveLength(3);
    const names = model.sections.map((s) => s.name);
    expect(names).toContain('Coluna');
    expect(names).toContain('TercaE');
    expect(names).toContain('TercaD');
  });

  it('Possui 6 nós (vértices da base + topo + cumeeira)', () => {
    expect(model.nodes).toHaveLength(6);
  });

  it('Nó 1 está na origem (0, 0)', () => {
    const n1 = model.nodes.find((n) => n.id === 1);
    expect(n1).toBeDefined();
    expect(n1?.x).toBe(0);
    expect(n1?.y).toBe(0);
  });

  it('Nó 3 está em (b, 0) = (15, 0)', () => {
    const n3 = model.nodes.find((n) => n.id === 3);
    expect(n3?.x).toBe(15);
    expect(n3?.y).toBe(0);
  });

  it('Nó 5 (cumeeira) tem altura h + rise', () => {
    const n5 = model.nodes.find((n) => n.id === 5);
    const expectedRise = (15 / 2) * Math.tan((10 * Math.PI) / 180);
    expect(n5?.x).toBe(7.5);
    expect(n5?.y).toBeCloseTo(6 + expectedRise, 3);
  });

  it('Possui 4 membros (2 colunas + 2 águas)', () => {
    expect(model.members).toHaveLength(4);
    const ids = model.members.map((m) => m.id);
    expect(ids).toEqual([1, 2, 3, 4]);
  });

  it('Membro 1 é a coluna esquerda (N1 → N4)', () => {
    const m1 = model.members.find((m) => m.id === 1);
    expect(m1?.nodeI).toBe(1);
    expect(m1?.nodeJ).toBe(4);
  });

  it('Membro 4 é a coluna direita (N6 → N3)', () => {
    const m4 = model.members.find((m) => m.id === 4);
    expect(m4?.nodeI).toBe(6);
    expect(m4?.nodeJ).toBe(3);
  });

  it('Possui 1 caso de carga (vento)', () => {
    expect(model.loadCases).toHaveLength(1);
    expect(model.loadCases[0].name).toContain('Vento');
  });

  it('Caso de carga tem 4 cargas (2 colunas + 2 águas)', () => {
    expect(model.loadCases[0].loads).toHaveLength(4);
  });

  it('Carga da coluna esquerda é empuxo (sinal negativo em GlobalX)', () => {
    const load = model.loadCases[0].loads.find((l) => l.memberId === 1);
    expect(load?.direction).toBe('GlobalX');
    expect(load?.type).toBe('Uniform');
  });
});

describe('M9.4 — serializeFtool (texto exportado)', () => {
  let txt: string;
  beforeEach(() => {
    const model = buildFtoolModel();
    txt = serializeFtool(model);
  });

  it('Contém cabeçalho VentoApp', () => {
    expect(txt).toContain('VentoApp');
    expect(txt).toContain('NBR 6123:2023');
  });

  it('Declara GENERAL com Units kN m', () => {
    expect(txt).toContain('GENERAL');
    expect(txt).toContain('Units kN m');
    expect(txt).toContain('EndGENERAL');
  });

  it('Declara MATERIAL com Id e propriedades', () => {
    expect(txt).toContain('MATERIAL');
    expect(txt).toMatch(/Id 1/);
    expect(txt).toMatch(/E [\d.eE+-]+/);
    expect(txt).toMatch(/Nu 0\.3/);
    expect(txt).toContain('EndMATERIAL');
  });

  it('Declara SECTION com A e Iz', () => {
    expect(txt).toContain('SECTION');
    expect(txt).toMatch(/A [\d.eE+-]+/);
    expect(txt).toMatch(/Iz [\d.eE+-]+/);
    expect(txt).toContain('EndSECTION');
  });

  it('Declara 6 NODE com Id X Y', () => {
    const nodeLines = txt.split('\n').filter((l) => l.match(/^Id \d+ X [\d.eE+-]+ Y [\d.eE+-]+$/));
    expect(nodeLines).toHaveLength(6);
    expect(txt).toContain('EndNODE');
  });

  it('Declara 4 MEMBER com NodeI NodeJ SectionId MaterialId', () => {
    expect(txt).toContain('MEMBER');
    expect(txt).toMatch(/NodeI \d+ NodeJ \d+/);
    expect(txt).toMatch(/SectionId \d+/);
    expect(txt).toMatch(/MaterialId \d+/);
    expect(txt).toContain('EndMEMBER');
  });

  it('Declara LOADCASE com MEMBERLOAD', () => {
    expect(txt).toContain('LOADCASE');
    expect(txt).toContain('MEMBERLOAD');
    expect(txt).toContain('EndMEMBERLOAD');
    expect(txt).toContain('EndLOADCASE');
  });

  it('Cargas de vento: Uniform com GlobalX (colunas) e GlobalY (terças)', () => {
    const loadLines = txt
      .split('\n')
      .filter((l) => l.includes('Uniform') && l.includes('Value'));
    expect(loadLines.length).toBeGreaterThanOrEqual(4);
    const hasGlobalX = loadLines.some((l) => l.includes('GlobalX'));
    const hasGlobalY = loadLines.some((l) => l.includes('GlobalY'));
    expect(hasGlobalX).toBe(true);
    expect(hasGlobalY).toBe(true);
  });

  it('Arquivo termina com \\n', () => {
    expect(txt.endsWith('\n')).toBe(true);
  });
});

describe('M9.4 — Robustez', () => {
  it('Material tem E positivo', () => {
    const m = buildFtoolModel();
    expect(m.materials[0].eKpa).toBeGreaterThan(0);
  });

  it('Seções têm A > 0 e Iz > 0', () => {
    const m = buildFtoolModel();
    m.sections.forEach((s) => {
      expect(s.aM2).toBeGreaterThan(0);
      expect(s.izM4).toBeGreaterThan(0);
    });
  });

  it('Caso de carga tem nome com q e Cpi', () => {
    const m = buildFtoolModel();
    expect(m.loadCases[0].name).toMatch(/q=/);
    expect(m.loadCases[0].name).toMatch(/Cpi=/);
  });

  it('Direções GlobalX e GlobalY presentes', () => {
    const m = buildFtoolModel();
    const dirs = new Set(m.loadCases[0].loads.map((l) => l.direction));
    expect(dirs.has('GlobalX')).toBe(true);
    expect(dirs.has('GlobalY')).toBe(true);
  });
});