import { fn } from '@vitest/spy';
import fetchMock from 'fetch-mock';
import { setTimeout } from 'node:timers/promises';
import { select as d3_select } from 'd3-selection';


describe('iD.serviceOsmWikibase', function () {
  var wikibase;

  beforeEach(function () {
    iD.services.osmWikibase = iD.serviceOsmWikibase;
  });

  afterEach(function () {
    delete iD.services.osmWikibase;
  });

  beforeEach(function () {
    wikibase = iD.services.osmWikibase;
    wikibase.init();
  });

  afterEach(function () {
    fetchMock.reset();
  });


  function parseQueryString(url) {
    return iD.utilStringQs(url.substring(url.indexOf('?')));
  }

  function keyData() {
    return {
      pageid: 205725,
      ns: 120,
      title: 'Item:Q42',
      lastrevid: 1721242,
      modified: '2018-12-18T07:00:43Z',
      type: 'item',
      id: 'Q42',
      labels: {
        fr: {language: 'en', value: 'amenity', 'for-language': 'fr'}
      },
      descriptions: {
        fr: {language: 'en', value: 'English description', 'for-language': 'fr'}
      },
      aliases: {},
      claims: {
        P2: [ // instance of
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q7'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P16: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'string',
              datavalue: {value: 'amenity', type: 'string'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P25: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q4679'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P9: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q8'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P6: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q15'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'preferred'
          },
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q14'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            qualifiers: {
              P26: [
                {
                  snaktype: 'value',
                  datatype: 'wikibase-item',
                  datavalue: {value: {'entity-type': 'item', id: 'Q6994'}, type: 'wikibase-entityid'}
                }
              ]
            },
            rank: 'normal'
          }
        ],
        P28: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'string',
              datavalue: {value: 'Mapping-Features-Parking-Lot.png', type: 'string'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ]
      },
      sitelinks: {
        wiki: {
          site: 'wiki',
          title: 'Key:amenity',
          badges: []
        }
      }
    };
  }

  function tagData() {
    return {
      pageid: 210934,
      ns: 120,
      title: 'Item:Q13',
      lastrevid: 1718041,
      modified: '2018-12-18T03:51:05Z',
      type: 'item',
      id: 'Q13',
      labels: {
        fr: {language: 'en', value: 'amenity=parking', 'for-language': 'fr'}
      },
      descriptions: {
        fr: {language: 'fr', value: 'French description'}
      },
      aliases: {},
      claims: {
        P2: [ // instance of = Q2 (tag)
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q2'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P19: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'string',
              datavalue: {value: 'amenity=parking', type: 'string'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P10: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q42'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P4: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'commonsMedia',
              datavalue: {value: 'Primary image.jpg', type: 'string'}
            },
            type: 'statement',
            rank: 'preferred'
          }
        ],
        P6: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q14'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'preferred'
          },
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q13'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            qualifiers: {
              P26: [
                {
                  snaktype: 'value',
                  datatype: 'wikibase-item',
                  datavalue: {value: {'entity-type': 'item', id: 'Q6994'}, type: 'wikibase-entityid'}
                }
              ]
            },
            rank: 'normal'
          }
        ],
        P25: [
          {
            mainsnak: {
              snaktype: 'value',
              datatype: 'wikibase-item',
              datavalue: {value: {'entity-type': 'item', id: 'Q4679'}, type: 'wikibase-entityid'}
            },
            type: 'statement',
            rank: 'normal'
          }
        ],
        P31: [
          {mainsnak: {datavalue: {value: {text: 'Cs:Key:bridge:movable', language: 'cs'}}}},
          {mainsnak: {datavalue: {value: {text: 'DE:Key:bridge:movable', language: 'de'}}}},
          {mainsnak: {datavalue: {value: {text: 'FR:Key:bridge:movable', language: 'fr'}}}},
          {mainsnak: {datavalue: {value: {text: 'JA:Key:bridge:movable', language: 'ja'}}}},
          {mainsnak: {datavalue: {value: {text: 'Pl:Key:bridge:movable', language: 'pl'}}}},
          {mainsnak: {datavalue: {value: {text: 'Key:bridge:movable', language: 'en'}}}},
        ],
      },
      sitelinks: {
        wiki: {
          site: 'wiki',
          title: 'Tag:amenity=parking',
          badges: []
        }
      }
    };
  }


  var localeData = {
    id: 'Q7792',
    sitelinks: {wiki: {site: 'wiki', title: 'Locale:fr'}}
  };

  describe('#getEntity', function () {
    it('calls the given callback with the results of the getEntity data item query', async () => {
      const callback = fn();
      fetchMock.mock(/action=wbgetentities/, {
        body: JSON.stringify({
          entities: {
            Q42: keyData(),
            Q13: tagData(),
            Q7792: localeData,
          },
          success: 1
        }),
        status: 200,
        headers: { 'Content-Type': 'application/json' }
      });

      wikibase.getEntity({ key: 'amenity', value: 'parking', langCodes: ['fr'] }, callback);

      await setTimeout(50);
      expect(parseQueryString(fetchMock.calls(/action=wbgetentities/)[0][0])).toEqual(
        {
          action: 'wbgetentities',
          sites: 'wiki',
          titles: 'Locale:fr|Key:amenity|Tag:amenity=parking',
          languages: 'fr',
          languagefallback: '1',
          origin: '*',
          format: 'json',
        }
      );
      expect(callback).toHaveBeenCalledWith(null, {
        key: keyData(),
        tag: tagData()
      });
      expect(wikibase.getLocaleIDs()).toStrictEqual({ fr: 'Q7792' });
    });
  });


  it('creates correct sitelinks', function () {
    expect(wikibase.toSitelink('amenity')).toEqual('Key:amenity');
    expect(wikibase.toSitelink('amenity_')).toEqual('Key:amenity');
    expect(wikibase.toSitelink('_amenity_')).toEqual('Key: amenity');
    expect(wikibase.toSitelink('amenity or_not_')).toEqual('Key:amenity or not');
    expect(wikibase.toSitelink('amenity', 'parking')).toEqual('Tag:amenity=parking');
    expect(wikibase.toSitelink(' amenity_', '_parking_')).toEqual('Tag: amenity = parking');
    expect(wikibase.toSitelink('amenity or_not', '_park ing_')).toEqual('Tag:amenity or not= park ing');
  });

  it('gets correct value from entity', function () {
    wikibase.addLocale('de', 'Q6994');
    wikibase.addLocale('fr', 'Q7792');
    expect(wikibase.claimToValue(tagData(), 'P4', 'en')).toEqual('Primary image.jpg');
    expect(wikibase.claimToValue(keyData(), 'P6', 'en')).toEqual('Q15');
    expect(wikibase.claimToValue(keyData(), 'P6', 'fr')).toEqual('Q15');
    expect(wikibase.claimToValue(keyData(), 'P6', 'de')).toEqual('Q14');
  });

  it('gets monolingual value from entity as an object', function () {
    expect(wikibase.monolingualClaimToValueObj(tagData(), 'P31')).toEqual({
      cs: 'Cs:Key:bridge:movable',
      de: 'DE:Key:bridge:movable',
      fr: 'FR:Key:bridge:movable',
      ja: 'JA:Key:bridge:movable',
      pl: 'Pl:Key:bridge:movable',
      en: 'Key:bridge:movable',
    });
  });

  describe('linkifyWikiText', () => {
    it('handles normal text', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText('hello'));

      expect(main.innerHTML).toBe('<span>hello</span>');
      expect(main.textContent).toBe('hello');
    });

    it('prevents XSS attacks', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText('123 <script>bad</script> 456'));

      expect(main.innerHTML).toBe('<span>123 &lt;script&gt;bad&lt;/script&gt; 456</span>');
      expect(main.textContent).toBe('123 <script>bad</script> 456');
    });

    it('linkifies the tag: and key: syntax', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText('use tag:natural=water with key:water instead'));

      expect(main.innerHTML).toBe([
        '<span>use </span>',
        '<a href="https://wiki.openstreetmap.org/wiki/Tag:natural=water" target="_blank" rel="noreferrer"><code>natural=water</code></a>',
        '<span> with </span>',
        '<a href="https://wiki.openstreetmap.org/wiki/Key:water" target="_blank" rel="noreferrer"><code>water=*</code></a>',
        '<span> instead</span>'
      ].join(''));
      expect(main.textContent).toBe('use natural=water with water=* instead');
    });

    it('works if the string is 100% a link', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText('tag:natural=water'));

      expect(main.innerHTML).toBe([
        '<a href="https://wiki.openstreetmap.org/wiki/Tag:natural=water" target="_blank" rel="noreferrer"><code>natural=water</code></a>',
      ].join(''));
      expect(main.textContent).toBe('natural=water');
    });

    it('works if the link is the first part of the string', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText('tag:craft=sailmaker is better'));

      expect(main.innerHTML).toBe([
        '<a href="https://wiki.openstreetmap.org/wiki/Tag:craft=sailmaker" target="_blank" rel="noreferrer"><code>craft=sailmaker</code></a>',
        '<span> is better</span>'
      ].join(''));
      expect(main.textContent).toBe('craft=sailmaker is better');
    });

    it('works if the link is the last part of the string', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText('prefer tag:craft=sailmaker'));

      expect(main.innerHTML).toBe([
        '<span>prefer </span>',
        '<a href="https://wiki.openstreetmap.org/wiki/Tag:craft=sailmaker" target="_blank" rel="noreferrer"><code>craft=sailmaker</code></a>',
      ].join(''));
      expect(main.textContent).toBe('prefer craft=sailmaker');
    });

    it('handles empty strings', () => {
      const main = document.createElement('main');
      d3_select(main).call(iD.serviceOsmWikibase.linkifyWikiText(''));

      expect(main.innerHTML).toBe('');
      expect(main.textContent).toBe('');
    });
  });
});
