import { test, expect } from '@playwright/test';
import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';

const HTML_PATH = path.resolve('landing/dist/index.html');
const FILE_URL = 'file://' + HTML_PATH;

// The landing page fetches its containing directory with Accept: application/json
// to get the project list. Chromium refuses fetch() on file:// URLs entirely, so
// page.route() can't help — we monkey-patch window.fetch in an init script and
// drive the response from window.__mockProjects, set per-test.

const FETCH_MOCK = `
    window.__mockProjects = [];
    const realFetch = window.fetch;
    window.fetch = async function(url, opts) {
        const accept = opts && opts.headers && opts.headers.Accept;
        if (accept && String(accept).includes('json')) {
            return new Response(JSON.stringify(window.__mockProjects), {
                status: 200,
                headers: { 'Content-Type': 'application/json' },
            });
        }
        return realFetch.apply(this, arguments);
    };
`;

const SAMPLE_PROJECTS = [
    { name: '176109', url: '/176109/', title: 'Greenfield Substation' },
    { name: '197072', url: '/197072/', title: 'Brownfield Tap' },
    { name: '210045', url: '/210045/', title: '' },
];

async function loadLandingWithProjects(page, projects) {
    await page.addInitScript({ content: FETCH_MOCK });
    await page.addInitScript(p => { window.__mockProjects = p; }, projects);
    await page.goto(FILE_URL, { waitUntil: 'domcontentloaded' });
}

test.describe('Landing page', () => {

    test('renders welcome hero and a project table when projects come back', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        await expect(page.locator('.landing-hero h1')).toContainText(/Welcome/i);
        const rowCount = await page.locator('.project-table tbody tr').count();
        expect(rowCount).toBe(3);
        await expect(page.locator('th.project-table-title-col')).toBeVisible();
        await expect(page.locator('.project-table tbody')).toContainText('Greenfield Substation');
        await expect(page.locator('.project-table-no-title')).toHaveCount(1);
    });

    test('default mode has no project checkbox column; click row opens that single project', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        // Default mode: no checkbox column on rows.
        await expect(page.locator('.project-table tbody td.project-table-checkbox-col')).toHaveCount(0);

        // Stub navigation so the click doesn't navigate the test page.
        await page.evaluate(() => {
            window.__navTo = null;
            window.LandingApp._setNavigate(url => { window.__navTo = url; });
        });

        await page.locator('.project-table-row[data-name="197072"]').click();

        const navTo = await page.evaluate(() => window.__navTo);
        // Single-project click navigates to the project's canonical subtree
        // (so the user can swap archive.html for working/, staging/, etc.).
        expect(navTo).toContain('/197072/archive.html');
        expect(navTo).not.toContain('?projects=');
    });

    test('column filters narrow the table; filters persist in URL', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        await page.locator('input.column-filter[data-column="pn"]').fill('176');
        await page.waitForTimeout(150);
        const rowsAfterPn = await page.locator('.project-table tbody tr').count();
        expect(rowsAfterPn).toBe(1);
        await expect(page.locator('.project-table tbody')).toContainText('176109');

        const search = await page.evaluate(() => location.search);
        expect(search).toContain('pn=176');

        await page.locator('input.column-filter[data-column="pn"]').fill('');
        await page.locator('input.column-filter[data-column="pt"]').fill('Brown');
        await page.waitForTimeout(150);
        await expect(page.locator('.project-table tbody')).toContainText('Brownfield Tap');
    });

    test('shows a friendly empty state when the server returns no projects', async ({ page }) => {
        await loadLandingWithProjects(page, []);
        await page.waitForSelector('.project-list-empty', { timeout: 5000 });

        await expect(page.locator('.project-list-empty')).toContainText(/No projects to show/);
        await expect(page.locator('.project-list-empty')).toContainText(/access/i);
    });

    test('"+ New group" enters select-mode: checkboxes appear, action bar shows', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        await expect(page.locator('#selectActionBar')).toBeHidden();
        await expect(page.locator('.project-table tbody td.project-table-checkbox-col')).toHaveCount(0);

        await page.locator('#newGroupBtn').click();

        await expect(page.locator('#selectActionBar')).toBeVisible();
        await expect(page.locator('.project-table tbody td.project-table-checkbox-col')).toHaveCount(3);
        await expect(page.locator('#groupNameInput')).toBeFocused();
    });

    test('Save group writes to localStorage and renders in the groups table', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        await page.locator('#newGroupBtn').click();
        // Click two project rows to check them.
        await page.locator('.project-table-row[data-name="176109"]').click();
        await page.locator('.project-table-row[data-name="197072"]').click();
        await page.locator('#groupNameInput').fill('Greenfield Sites');
        await page.locator('#saveGroupBtn').click();

        // Action bar closed, group rendered in groups table.
        await expect(page.locator('#selectActionBar')).toBeHidden();
        await expect(page.locator('.groups-table')).toContainText('Greenfield Sites');
        await expect(page.locator('.groups-row[data-name="Greenfield Sites"] .groups-row-count')).toContainText('2 projects');

        const stored = await page.evaluate(() => localStorage.getItem('zddc_landing_groups'));
        expect(stored).toContain('Greenfield Sites');
        expect(stored).toContain('176109');
        expect(stored).toContain('197072');
    });

    test('clicking a group row opens archive with that group\'s projects', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        // Seed a group via storage and reload.
        await page.evaluate(() => {
            localStorage.setItem('zddc_landing_groups', JSON.stringify([
                { name: 'Both', projects: ['176109', '197072'] }
            ]));
        });
        await page.reload({ waitUntil: 'domcontentloaded' });
        await page.waitForSelector('.groups-row', { timeout: 5000 });

        await page.evaluate(() => {
            window.__navTo = null;
            window.LandingApp._setNavigate(url => { window.__navTo = url; });
        });

        await page.locator('.groups-row[data-name="Both"]').click();
        const navTo = await page.evaluate(() => window.__navTo);
        expect(navTo).toMatch(/archive\.html\?projects=176109,197072|archive\.html\?projects=197072,176109/);
    });

    test('delete button removes a group', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.evaluate(() => {
            localStorage.setItem('zddc_landing_groups', JSON.stringify([
                { name: 'ToGo', projects: ['176109'] }
            ]));
        });
        await page.reload({ waitUntil: 'domcontentloaded' });
        await page.waitForSelector('.groups-row', { timeout: 5000 });

        // Auto-confirm the confirm() dialog.
        page.once('dialog', d => d.accept());
        await page.locator('.groups-row[data-name="ToGo"] .groups-btn-delete').click();

        await expect(page.locator('.groups-row[data-name="ToGo"]')).toHaveCount(0);
        const stored = await page.evaluate(() => localStorage.getItem('zddc_landing_groups'));
        expect(stored).not.toContain('ToGo');
    });

    test('edit button enters select-mode pre-populated with that group\'s projects', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.evaluate(() => {
            localStorage.setItem('zddc_landing_groups', JSON.stringify([
                { name: 'OnlyOne', projects: ['176109'] }
            ]));
        });
        await page.reload({ waitUntil: 'domcontentloaded' });
        await page.waitForSelector('.groups-row', { timeout: 5000 });

        await page.locator('.groups-row[data-name="OnlyOne"] .groups-btn-edit').click();

        await expect(page.locator('#selectActionBar')).toBeVisible();
        await expect(page.locator('#groupNameInput')).toHaveValue('OnlyOne');
        // Pre-checked: 176109 only.
        const checked = page.locator('.project-table tbody input[type="checkbox"]:checked');
        await expect(checked).toHaveCount(1);
        const checkedValue = await checked.getAttribute('value');
        expect(checkedValue).toBe('176109');
    });

    test('"Open selected" excludes filtered-out checked projects', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        await page.waitForSelector('.project-table', { timeout: 5000 });

        await page.locator('#newGroupBtn').click();
        // Check two: 176109 and 197072.
        await page.locator('.project-table-row[data-name="176109"]').click();
        await page.locator('.project-table-row[data-name="197072"]').click();

        // Filter to hide 197072 (its name doesn't contain "176").
        await page.locator('input.column-filter[data-column="pn"]').fill('176');
        await page.waitForTimeout(150);

        // Stub navigation.
        await page.evaluate(() => {
            window.__navTo = null;
            window.LandingApp._setNavigate(url => { window.__navTo = url; });
        });

        await page.locator('#openSelectedBtn').click();

        const navTo = await page.evaluate(() => window.__navTo);
        // After the visibility-filter trims to one project, this collapses
        // to the single-project path (no ?projects= form).
        expect(navTo).toContain('/176109/archive.html');
        expect(navTo).not.toContain('197072');
        expect(navTo).not.toContain('?projects=');
    });

    test('project mode: detectMode classifies URLs correctly', async ({ page }) => {
        await loadLandingWithProjects(page, []);
        const result = await page.evaluate(() => ({
            picker: window.LandingApp.detectMode === undefined ? 'no-fn' : null,
        }));
        // Sanity: the project-mode entry points are exposed.
        expect(result.picker).toBeNull();
    });
});

// project-mode tests need a real http(s) origin so location.pathname can be
// /<project>. Spin up a tiny in-process server that serves the same
// landing HTML at any path.
test.describe('Landing project mode', () => {
    let server;
    let baseUrl;

    test.beforeAll(async () => {
        const html = fs.readFileSync(HTML_PATH, 'utf8');
        server = http.createServer((req, res) => {
            // The page itself fetches /<project>/archive/ for the
            // party listing. Stub that with a small JSON listing so the
            // direct-link section renders. Anything else returns the
            // landing HTML.
            if (req.url === '/Project-1/archive/' &&
                (req.headers.accept || '').includes('json')) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify([
                    { name: 'PartyA/', is_dir: true, size: 0, url: '/Project-1/Archive/PartyA/' },
                    { name: 'PartyB/', is_dir: true, size: 0, url: '/Project-1/Archive/PartyB/' },
                    { name: '.hidden/', is_dir: true, size: 0, url: '/Project-1/Archive/.hidden/' },
                ]));
                return;
            }
            res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
            res.end(html);
        });
        await new Promise(r => server.listen(0, '127.0.0.1', r));
        baseUrl = `http://127.0.0.1:${server.address().port}`;
    });

    test.afterAll(async () => {
        if (server) await new Promise(r => server.close(r));
    });

    test('renders project workspace at /<project>', async ({ page }) => {
        await page.goto(`${baseUrl}/Project-1`, { waitUntil: 'load' });
        await page.waitForSelector('#projectView:not(.hidden)', { timeout: 5000 });

        // Project name surfaces in the H1 + the document title.
        await expect(page.locator('#projectName')).toHaveText('Project-1');
        expect(await page.title()).toBe('Project-1 — ZDDC');

        // Picker view is hidden.
        await expect(page.locator('#pickerView')).toBeHidden();

        // Four stage cards with the expected hrefs.
        const stageHrefs = await page.evaluate(() => ({
            archive:   document.getElementById('stageArchive').getAttribute('href'),
            working:   document.getElementById('stageWorking').getAttribute('href'),
            staging:   document.getElementById('stageStaging').getAttribute('href'),
            reviewing: document.getElementById('stageReviewing').getAttribute('href'),
        }));
        expect(stageHrefs).toEqual({
            archive:   '/Project-1/archive',
            working:   '/Project-1/working',
            staging:   '/Project-1/staging',
            reviewing: '/Project-1/reviewing',
        });
    });

    test('MDL card lists existing parties in a dropdown', async ({ page }) => {
        await page.goto(`${baseUrl}/Project-1`, { waitUntil: 'load' });
        // Wait for the async party fetch to populate the select.
        await page.waitForFunction(() => {
            const sel = document.getElementById('mdlPartySelect');
            return sel && !sel.disabled && sel.options.length > 1;
        }, { timeout: 5000 });

        const options = await page.locator('#mdlPartySelect option').allTextContents();
        // First option is the placeholder; the rest are party names.
        expect(options[0]).toBe('Choose a party…');
        expect(options.slice(1).sort()).toEqual(['PartyA', 'PartyB']);

        // Selecting a party enables the Open button; clicking it navigates
        // to the canonical /<project>/archive/<party>/mdl/ URL.
        await page.selectOption('#mdlPartySelect', 'PartyA');
        await expect(page.locator('#mdlOpenBtn')).toBeEnabled();
        const [navUrl] = await Promise.all([
            page.waitForURL(/\/Project-1\/archive\/PartyA\/mdl\/$/, { timeout: 5000 }).then(() => page.url()),
            page.click('#mdlOpenBtn'),
        ]);
        expect(navUrl).toMatch(/\/Project-1\/archive\/PartyA\/mdl\/$/);
    });

    test('legacy presets are migrated to groups on first load', async ({ page }) => {
        await loadLandingWithProjects(page, SAMPLE_PROJECTS);
        // Seed legacy presets and clear the new key.
        await page.evaluate(() => {
            localStorage.removeItem('zddc_landing_groups');
            localStorage.setItem('zddc_landing_presets', JSON.stringify([
                { name: 'Old Preset', state: { projects: ['176109', '210045'], pn: 'foo', sort: 'name' } }
            ]));
        });
        await page.reload({ waitUntil: 'domcontentloaded' });
        await page.waitForSelector('.groups-row', { timeout: 5000 });

        await expect(page.locator('.groups-row[data-name="Old Preset"]')).toBeVisible();
        const stored = await page.evaluate(() => localStorage.getItem('zddc_landing_groups'));
        expect(stored).toContain('Old Preset');
        expect(stored).toContain('176109');
        expect(stored).toContain('210045');
        // Filter/sort metadata should NOT carry over.
        expect(stored).not.toContain('foo');
    });
});
