const assert = require('assert');
const { UAParser } = require('../../src/main/ua-parser');
const { BrowserName, CPUArch, DeviceType, DeviceVendor, EngineName, OSName } = require('../../src/enums/ua-parser-enums');
const UACHTests = require('../data/ua-ch/headers');

describe('Map UA-CH headers', () => {

    const headers = {
        'sec-ch-ua' : '"Chromium";v="93", "Google Chrome";v="93", " Not;A Brand";v="99"',
        'sec-ch-ua-full-version-list' : '"Chromium";v="93.0.1.2", "Google Chrome";v="93.0.1.2", " Not;A Brand";v="99.0.1.2"',
        'sec-ch-ua-arch' : '"arm"',
        'sec-ch-ua-bitness' : '"64"',
        'sec-ch-ua-mobile' : '?1',
        'sec-ch-ua-model' : '"Pixel 99"',
        'sec-ch-ua-platform' : '"Windows"',
        'sec-ch-ua-platform-version' : '"13"',
        'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
    };
    
    let uap = UAParser(headers).withClientHints();
    let browser = new UAParser(headers).getBrowser().withClientHints();
    let cpu = new UAParser(headers).getCPU().withClientHints();
    let device = new UAParser(headers).getDevice().withClientHints();
    let engine = new UAParser(headers).getEngine().withClientHints();
    let os = new UAParser(headers).getOS().withClientHints();

    it('Can read from client-hints headers using `withClientHints()`', () => {  

        assert.strictEqual(uap.ua, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36");
        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, "93.0.1.2");
        assert.strictEqual(uap.browser.major, "93");        
        assert.strictEqual(browser.name, BrowserName.CHROME);
        assert.strictEqual(browser.version, "93.0.1.2");
        assert.strictEqual(browser.major, "93");
        assert.strictEqual(uap.cpu.architecture, CPUArch.ARM_64);
        assert.strictEqual(cpu.architecture, CPUArch.ARM_64);
        assert.strictEqual(uap.device.type, DeviceType.MOBILE);
        assert.strictEqual(uap.device.model, "Pixel 99");
        assert.strictEqual(uap.device.vendor, DeviceVendor.GOOGLE);
        assert.strictEqual(device.type, DeviceType.MOBILE);
        assert.strictEqual(device.model, "Pixel 99");
        assert.strictEqual(device.vendor, DeviceVendor.GOOGLE);
        assert.strictEqual(uap.engine.name, EngineName.BLINK);
        assert.strictEqual(uap.engine.version, '93.0.1.2');
        assert.strictEqual(engine.name, EngineName.BLINK);
        assert.strictEqual(engine.version, '93.0.1.2');
        assert.strictEqual(uap.os.name, OSName.WINDOWS);
        assert.strictEqual(uap.os.version, "11");
        assert.strictEqual(os.name, OSName.WINDOWS);
        assert.strictEqual(os.version, "11");
    });

    it('Only read from user-agent header when called without `withClientHints()`', () => { 

        uap = UAParser(headers);
        browser = new UAParser(headers).getBrowser();
        cpu = new UAParser(headers).getCPU();
        device = new UAParser(headers).getDevice();
        engine = new UAParser(headers).getEngine();
        os = new UAParser(headers).getOS();

        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, "110.0.0.0");
        assert.strictEqual(uap.browser.major, "110");
        assert.strictEqual(uap.cpu.architecture, CPUArch.X86_64);
        assert.strictEqual(uap.device.type, undefined);
        assert.strictEqual(uap.device.model, undefined);
        assert.strictEqual(uap.device.vendor, undefined);
        assert.strictEqual(uap.engine.name, EngineName.BLINK);
        assert.strictEqual(uap.engine.version, '110.0.0.0');
        assert.strictEqual(uap.os.name, OSName.LINUX);
        assert.strictEqual(uap.os.version, undefined);
    });

    it('Fallback to user-agent header when using `withClientHints()` but found no client hints-related headers',  () => {  

        const headers2 = {
            'sec-ch-ua-mobile' : '?1',
            'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
        };
        
        uap = UAParser(headers2).withClientHints();

        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, "110.0.0.0");
        assert.strictEqual(uap.browser.major, "110");
        assert.strictEqual(uap.cpu.architecture, CPUArch.X86_64);
        assert.strictEqual(uap.device.type, DeviceType.MOBILE);
        assert.strictEqual(uap.device.model, undefined);
        assert.strictEqual(uap.device.vendor, undefined);
        assert.strictEqual(uap.engine.name, EngineName.BLINK);
        assert.strictEqual(uap.engine.version, '110.0.0.0');
        assert.strictEqual(uap.os.name, OSName.LINUX);
        assert.strictEqual(uap.os.version, undefined);
    });

    it('Can detect Apple silicon from client hints data', () => {  

        // https://github.com/faisalman/ua-parser-js/issues/489#issuecomment-1479213579
        const httpHeadersFromAppleSilicon = {
            'sec-ch-ua-arch' : 'arm',
            'sec-ch-ua-platform' : 'macOS',
            'sec-ch-ua-mobile' : '?0',
            'sec-ch-ua' : '"Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
            'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0'
        };
        
        UAParser(httpHeadersFromAppleSilicon).withClientHints().then(ua => {

            // Only works in Chrome
            /* 
                if (ua.os.is("macOS") && 
                    ua.cpu.is("arm") &&
                    !ua.device.is("mobile") && 
                    !ua.device.is("tablet")) {
                        // possibly an Apple silicon device
                    }
            */

            assert.strictEqual(ua.os.is(OSName.MACOS), true);
            assert.strictEqual(ua.cpu.is(CPUArch.ARM), true);            
            assert.strictEqual(ua.device.is(DeviceType.MOBILE), false);
            assert.strictEqual(ua.device.is(DeviceType.TABLET), false);
        });
    });

    it('Can detect form-factors from client-hints', () => {  

        const FFVR = {
            'sec-ch-ua-form-factors' : '"VR"'
        };

        const FFEInk = {
            'sec-ch-ua-form-factors' : '"Tablet", "EInk"'
        };

        const FFUnknown = {
            'sec-ch-ua-form-factors' : '"Unknown"'
        };
        
        UAParser(FFVR).withClientHints().then(ua => {
            assert.strictEqual(ua.device.type, DeviceType.XR);
        });

        UAParser(FFEInk).withClientHints().then(ua => {
            assert.strictEqual(ua.device.type, DeviceType.TABLET);
        });


        UAParser(FFUnknown).withClientHints().then(ua => {
            assert.strictEqual(ua.device.type, undefined);
        });
    });

    it('Avoid error on headers variation', () => {  

        const headers2 = {
            'sec-ch-ua' : '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
            'sec-ch-ua-full-version-list' : '"Google Chrome", "Chromium", "Not?A_Brand";v="24.0.0.0"',
            'sec-ch-ua-full-version' : '""',
            'sec-ch-ua-mobile' : '?0',
            'sec-ch-ua-arch' : '""',
            'sec-ch-ua-bitness' : '""',
            'sec-ch-ua-model' : '""',
            'sec-ch-ua-platform' : '"Windows"',
            'sec-ch-ua-platform-version' : '""',
            'sec-ch-ua-wow64' : '?0',
        };
        
        uap = UAParser(headers2).withClientHints();

        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, undefined);
        assert.strictEqual(uap.browser.major, undefined);
    });

    it('Prioritize more specific brand name regardless the order', () => {  

        const headers3a = {
            'sec-ch-ua-full-version-list' : '"Not_A Brand;v=8, Chromium;v=120.0.6099.131, Google Chrome;v=120.0.6099.132"'
        };
        const headers3b = {
            'sec-ch-ua-full-version-list' : '"Chromium;v=120.0.6099.131, Not_A Brand;v=8, Google Chrome;v=120.0.6099.132"'
        };
        const headers3c = {
            'sec-ch-ua-full-version-list' : '"Google Chrome;v=120.0.6099.132, Chromium;v=120.0.6099.131, Not_A Brand;v=8"'
        };
        const headers3d = {
            'sec-ch-ua-full-version-list' : '"Microsoft Edge;v=120.0.6099.133, Google Chrome;v=120.0.6099.132, Chromium;v=120.0.6099.131, Not_A Brand;v=8"'
        };
        const headers3e = {
            'sec-ch-ua-full-version-list' : '"Chromium;v=120.0.6099.131, Google Chrome;v=120.0.6099.132, Microsoft Edge;v=120.0.6099.133, Not_A Brand;v=8"'
        };
        const headers3f = {
            'sec-ch-ua-full-version-list' : '"Not_A Brand;v=8, Microsoft Edge;v=120.0.6099.133, Google Chrome;v=120.0.6099.132, Chromium;v=120.0.6099.131"'
        };
        
        uap = UAParser(headers3a).withClientHints();
        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, "120.0.6099.132");

        uap = UAParser(headers3b).withClientHints();
        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, "120.0.6099.132");

        uap = UAParser(headers3c).withClientHints();
        assert.strictEqual(uap.browser.name, BrowserName.CHROME);
        assert.strictEqual(uap.browser.version, "120.0.6099.132");

        uap = UAParser(headers3d).withClientHints();
        assert.strictEqual(uap.browser.name, BrowserName.EDGE);
        assert.strictEqual(uap.browser.version, "120.0.6099.133");

        uap = UAParser(headers3e).withClientHints();
        assert.strictEqual(uap.browser.name, BrowserName.EDGE);
        assert.strictEqual(uap.browser.version, "120.0.6099.133");

        uap = UAParser(headers3f).withClientHints();
        assert.strictEqual(uap.browser.name, BrowserName.EDGE);
        assert.strictEqual(uap.browser.version, "120.0.6099.133");
    });
});

describe('UA-CH Headers tests', () => {
    UACHTests.forEach(test => {
        it(`Test for ${test.desc}`, () => {
            const { browser } = UAParser(test.headers).withClientHints();
            assert.deepEqual(browser, test.expect.browser);
        });
    });
});

describe('Identify vendor & type of device from given model name', () => {
    [
        {
            model: '220733SG',
            expect: {
                vendor : DeviceVendor.XIAOMI,
                type : DeviceType.MOBILE
            }
        },
        {
            model: '5087Z',
            expect: {
                vendor : DeviceVendor.TCL,
                type : DeviceType.MOBILE
            }
        },
        {
            model: '9137W',
            expect: {
                vendor : DeviceVendor.TCL,
                type : DeviceType.TABLET
            }
        },
        {
            model: 'BE2015',
            expect: {
                vendor : DeviceVendor.ONEPLUS,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'CPH2389',
            expect: {
                vendor : DeviceVendor.ONEPLUS,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'Infinix X669C',
            expect: {
                vendor : DeviceVendor.INFINIX,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'itel L6502',
            expect: {
                vendor : DeviceVendor.ITEL,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'Lenovo TB-X606F',
            expect: {
                vendor : DeviceVendor.LENOVO,
                type : DeviceType.TABLET
            }
        },
        {
            model: 'LM-Q720',
            expect: {
                vendor : DeviceVendor.LG,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'M2003J15SC',
            expect: {
                vendor : DeviceVendor.XIAOMI,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'MAR-LX1A',
            expect: {
                vendor : DeviceVendor.HUAWEI,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'moto g(20)',
            expect: {
                vendor : DeviceVendor.MOTOROLA,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'Nokia C210',
            expect: {
                vendor : DeviceVendor.NOKIA,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'Pixel 8',
            expect: {
                vendor : DeviceVendor.GOOGLE,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'Redmi Note 9S',
            expect: {
                vendor : DeviceVendor.XIAOMI,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'RMX3830',
            expect: {
                vendor : DeviceVendor.REALME,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'SM-S536DL',
            expect: {
                vendor : DeviceVendor.SAMSUNG,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'SM-S546VL',
            expect: {
                vendor : DeviceVendor.SAMSUNG,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'SM-T875',
            expect: {
                vendor : DeviceVendor.SAMSUNG,
                type : DeviceType.TABLET
            }
        },
        {
            model: 'STK-L21',
            expect: {
                vendor : DeviceVendor.HUAWEI,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'T430W',
            expect: {
                vendor : DeviceVendor.TCL,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'TECNO KI5k',
            expect: {
                vendor : DeviceVendor.TECNO,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'vivo 1820',
            expect: {
                vendor : DeviceVendor.VIVO,
                type : DeviceType.MOBILE
            }
        },
        {
            model: 'Xbox',
            expect: {
                vendor : DeviceVendor.MICROSOFT,
                type : DeviceType.CONSOLE
            }
        }
    ]
    .forEach(test => {
        it(`Solve "${test.model}"`, () => {
            const headers = {
                'sec-ch-ua-model' : test.model,
            };
            const { device } = UAParser(headers).withClientHints();
            assert.strictEqual(device.model, test.model);
            assert.strictEqual(device.vendor, test.expect.vendor);
            assert.strictEqual(device.type, test.expect.type);
        });
    });
});

describe('Chaining withClientHints() & withFeatureCheck() in server-side development', () => {
    const headers = {
        'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
        'sec-ch-ua-form-factors' : '"VR"'
    };
    const device = new UAParser(headers).getDevice();
    it('Chain order: withFeatureCheck().withClientHints()', () => {  
        const fc2ch = device.withFeatureCheck().withClientHints();
        assert.strictEqual(fc2ch.type, "xr");
    });
    it('Chain order: withClientHints().withFeatureCheck()', () => {
        const ch2fc = device.withClientHints().withFeatureCheck();
        assert.strictEqual(ch2fc.type, "xr");
    });
});