import { describe, expect, it, vi } from 'vitest';
import { frontEndContentCollectionTypes, gatherAuditEntities, runAndPersistAudit, documentToAuditEntity, } from '../../seo/audit-runner.js';
import { setActuateConfig } from '../../config/runtime.js';
/**
 * Regression coverage for the SEO audit reliability fixes:
 *  - a new audit refuses to start while another is RUNNING (parallel runs can
 *    otherwise duplicate SeoIssue rows for the same issueKey), and
 *  - `gatherAuditEntities` bounds its document query so a large instance can't
 *    OOM the audit pass.
 */
function baseDb(overrides = {}) {
    return {
        document: {
            findMany: vi.fn(async () => []),
        },
        seoAuditRun: {
            create: vi.fn(async () => ({ id: 'run-1' })),
            update: vi.fn(async () => ({})),
            findFirst: vi.fn(async () => null),
        },
        seoIssue: {
            findMany: vi.fn(async () => []),
            create: vi.fn(async () => ({})),
            update: vi.fn(async () => ({})),
        },
        ...overrides,
    };
}
describe('runAndPersistAudit concurrency guard', () => {
    it('throws when another audit started within the stale window', async () => {
        const db = baseDb();
        // First findFirst call is the RUNNING check; return a fresh running run.
        db.seoAuditRun.findFirst = vi.fn(async ({ where }) => {
            if (where?.status === 'RUNNING')
                return { startedAt: new Date() };
            return null;
        });
        await expect(runAndPersistAudit(db)).rejects.toThrow(/already running/i);
        expect(db.seoAuditRun.create).not.toHaveBeenCalled();
    });
    it('proceeds when the only RUNNING run is stale (crashed)', async () => {
        const db = baseDb();
        const stale = new Date(Date.now() - 60 * 60 * 1000); // 1h ago
        db.seoAuditRun.findFirst = vi.fn(async ({ where }) => {
            if (where?.status === 'RUNNING')
                return { startedAt: stale };
            if (where?.status === 'COMPLETED')
                return { siteScoreAfter: 80 };
            return null;
        });
        const result = await runAndPersistAudit(db);
        expect(db.seoAuditRun.create).toHaveBeenCalledTimes(1);
        expect(result.status).toBe('COMPLETED');
    });
    it('proceeds when no audit is running', async () => {
        const db = baseDb();
        const result = await runAndPersistAudit(db);
        expect(db.seoAuditRun.create).toHaveBeenCalledTimes(1);
        expect(result.status).toBe('COMPLETED');
    });
});
describe('gatherAuditEntities query bounds', () => {
    it('caps the document query with a take limit', async () => {
        const findMany = vi.fn(async (_args) => []);
        const db = { document: { findMany } };
        await gatherAuditEntities(db);
        expect(findMany).toHaveBeenCalledTimes(1);
        const arg = (findMany.mock.calls[0]?.[0] ?? {});
        expect(typeof arg.take).toBe('number');
        expect(arg.take).toBeGreaterThan(0);
    });
    it('excludes internal/system collections from the audit set', async () => {
        const findMany = vi.fn(async () => [
            { id: 'a', collection: 'pages', data: { title: 'A', slug: 'a' } },
            { id: 'b', collection: '__seo_config__', data: { title: 'B', slug: 'b' } },
        ]);
        const db = { document: { findMany } };
        const entities = await gatherAuditEntities(db);
        expect(entities).toHaveLength(1);
        expect(entities[0].entityId).toBe('a');
    });
    it('scopes the query to front-end content collections when given a type map', async () => {
        const findMany = vi.fn(async (_args) => []);
        const db = { document: { findMany } };
        await gatherAuditEntities(db, { collectionTypes: { pages: 'page', blog: 'post' } });
        const arg = (findMany.mock.calls[0]?.[0] ?? {});
        expect(arg.where?.collection?.in).toEqual(['pages', 'blog']);
    });
    it('returns nothing (without querying) when there are no front-end collections', async () => {
        const findMany = vi.fn(async () => []);
        const db = { document: { findMany } };
        const entities = await gatherAuditEntities(db, { collectionTypes: {} });
        expect(entities).toEqual([]);
        expect(findMany).not.toHaveBeenCalled();
    });
    it('derives entityType from the configured collection type', async () => {
        const findMany = vi.fn(async () => [
            { id: 'p1', collection: 'pages', data: { title: 'Home', slug: 'home' } },
            { id: 'b1', collection: 'blog', data: { title: 'Post', slug: 'post' } },
        ]);
        const db = { document: { findMany } };
        const entities = await gatherAuditEntities(db, {
            collectionTypes: { pages: 'page', blog: 'post' },
        });
        expect(entities.find((e) => e.entityId === 'p1').entityType).toBe('page');
        expect(entities.find((e) => e.entityId === 'b1').entityType).toBe('post');
    });
    it('infers structuredDataType from collection defaults for audit scoring', async () => {
        setActuateConfig({
            collections: {
                pages: { slug: 'pages', type: 'page', fields: [], seo: { defaultSchemaType: 'WebPage' } },
            },
        });
        const findMany = vi.fn(async () => [
            {
                id: 'p1',
                collection: 'pages',
                data: { title: 'Home', slug: 'home' },
                structuredData: null,
            },
        ]);
        const db = { document: { findMany } };
        const entities = await gatherAuditEntities(db, { collectionTypes: { pages: 'page' } });
        expect(entities[0].structuredDataType).toBe('WebPage');
        setActuateConfig({});
    });
    it('does not emit missing-structured-data when schema type is inferred', async () => {
        const { runSeoAuditAnalysis } = await import('../../seo/audit-engine.js');
        setActuateConfig({
            collections: {
                pages: { slug: 'pages', type: 'page', fields: [], seo: { defaultSchemaType: 'WebPage' } },
            },
        });
        const findMany = vi.fn(async () => [
            {
                id: 'p1',
                collection: 'pages',
                title: 'Home',
                slug: 'home',
                data: {
                    title: 'Home',
                    slug: 'home',
                    metaTitle: 'Home — Acme',
                    metaDescription: 'Welcome to Acme, the modern CMS for teams shipping reliable content.',
                    canonicalUrl: 'https://example.com/',
                    ogImage: 'https://example.com/og.png',
                },
                structuredData: null,
                plainText: 'word '.repeat(400),
            },
        ]);
        const db = { document: { findMany } };
        const entities = await gatherAuditEntities(db, { collectionTypes: { pages: 'page' } });
        const result = runSeoAuditAnalysis(entities);
        expect(result.issues.some((i) => i.type === 'missing-structured-data')).toBe(false);
        setActuateConfig({});
    });
    it('resolves urlPrefix paths and auto-canonical for audit entities', () => {
        setActuateConfig({
            seo: { siteUrl: 'https://example.com' },
            collections: {
                blog: { slug: 'blog', type: 'post', urlPrefix: 'blog', fields: [] },
            },
        });
        const entity = documentToAuditEntity({
            id: 'post-1',
            collection: 'blog',
            slug: 'hello-world',
            data: { title: 'Hello', slug: 'hello-world' },
            structuredData: null,
        });
        expect(entity.url).toBe('/blog/hello-world');
        expect(entity.canonicalUrl).toBe('https://example.com/blog/hello-world');
        setActuateConfig({});
    });
});
describe('frontEndContentCollectionTypes', () => {
    const cfg = {
        collections: {
            pages: { type: 'page' },
            blog: { type: 'post' },
            'case-studies': { type: 'post' },
            // Structural / utility collections have no `type` -> excluded.
            navigations: { admin: { group: 'Content' } },
            'page-tags': { admin: { hidden: true } },
            // Hidden even though typed -> excluded.
            drafts: { type: 'post', admin: { hidden: true } },
            // Explicitly excluded from sitemap -> excluded.
            legal: { type: 'page', seo: { excludeFromSitemap: true } },
        },
        seo: { sitemap: { excludeCollections: ['case-studies'] } },
    };
    it('returns only typed, visible, sitemap-eligible content collections', () => {
        expect(frontEndContentCollectionTypes(cfg)).toEqual({ pages: 'page', blog: 'post' });
    });
    it('excludes navigation/footer (untyped) collections', () => {
        expect(frontEndContentCollectionTypes(cfg)).not.toHaveProperty('navigations');
    });
    it('returns an empty map for missing/empty config', () => {
        expect(frontEndContentCollectionTypes(null)).toEqual({});
        expect(frontEndContentCollectionTypes({})).toEqual({});
    });
});
//# sourceMappingURL=audit-runner.test.js.map