import json
import pywikibot
import re
import requests
import time
from pywikibot import pagegenerators

wikidata_site = pywikibot.Site('wikidata', 'wikidata')
repo = wikidata_site.data_repository()
timestampformat='%Y-%m-%d %H:%M:%S'

claimProperties = [ 'P235' ] #'*' ] # references in these claims are looked at; '*' for all properties, or a specified list
fromRefProperty = 'P248'
requireFromRefValue = [ 'Q48798' ] # None (just moves everything) or list of Q-ID (recommended)
toRefProperty = 'P248'
setToRefValue = 'Q59911453' # None or Q-ID; this replaces the target value of the moved reference qualifier

editSummary = 'update reference: move [[Property:P248]] reference qualifier value to [[Q59911453]]'

dataset_query = """SELECT ?item WHERE {
  ?item ?any [ prov:wasDerivedFrom [ pr:P2566 []; pr:P248 wd:Q48798 ] ] .
}"""
inputdata = [  ]
for itemkey in pagegenerators.WikidataSPARQLPageGenerator(dataset_query, site=wikidata_site):
    inputdata.append(itemkey.title())

print('Found {} items to process'.format(len(inputdata)))

def action_moveP(Qitem, claimProps): # adapted from https://github.com/Pascalco/DeltaBot/blob/master/fixClaims/fixClaims.py#L223 on 2017-03-06
    commands = {} # define a command structure to be filled; this is the "data" parameter of the API at https://www.mediawiki.org/wiki/Wikibase/API#wbeditentity
    commands['claims'] = [] # we only work on claims in this script; this list takes all claim commands

    if claimProps==[ '*' ]:
        claimProps.pop(claimProps.index('*'))
        for key in Qitem.claims.keys():
            claimProps.append(key)
            
    for claimProperty in claimProps:
        if claimProperty not in Qitem.claims:
            continue
        for claim in Qitem.claims[claimProperty]:
            claimValue = claim.getTarget()
            claimHasChanged = False
            fromClaimJSON = claim.toJSON()
            if 'references' not in fromClaimJSON: # no references found
                continue 
                
            for i, reference in enumerate(fromClaimJSON['references']):
                if fromRefProperty not in fromClaimJSON['references'][i]['snaks']: # fromRefProperty not found
                    continue
                if 'P2566' not in fromClaimJSON['references'][i]['snaks']:
                    continue
                if requireFromRefValue!=None and 'Q' + str(fromClaimJSON['references'][i]['snaks'][fromRefProperty][0]['datavalue']['value']['numeric-id']) not in requireFromRefValue: # target value of the reference qualifier to move is wrong
                    continue
                    
                claimHasChanged = True
                fromClaimJSON['references'][i]['snaks'][toRefProperty] = fromClaimJSON['references'][i]['snaks'].pop(fromRefProperty)
                for j, elem in enumerate(fromClaimJSON['references'][i]['snaks'][toRefProperty]):
                    fromClaimJSON['references'][i]['snaks'][toRefProperty][j]['property'] = toRefProperty
                    if setToRefValue!=None: # change target Q-ID
                        fromClaimJSON['references'][i]['snaks'][toRefProperty][j]['datavalue']['value']['numeric-id'] = int(setToRefValue[1:])
                fromClaimJSON['references'][i]['snaks-order'][fromClaimJSON['references'][i]['snaks-order'].index(fromRefProperty)] = toRefProperty

            if claimHasChanged==True:
                commands['claims'].append({'id': fromClaimJSON['id'], 'remove':''}) # add first command: removal of the old claim, identified by statement ID
                fromClaimJSON.pop('id', None) # drop statement ID from the old claim, since a new one needs to be generated for the new claim
                commands['claims'].append(fromClaimJSON) # add second command: addition of the new (modified) claim, with data from the old claim (old ID was removed, new property identifier set)
            
    if len(commands['claims']) > 0: # all commands are executed here
        try:
            Qitem.editEntity(commands, summary=editSummary)
            pass
        except Exception as e:
            print(e)
            time.sleep(60) # take a 1 minute break, in order to drastically reduce load in case something goes wrong
    return int(0.5 * len(commands['claims'])) # return number of moved claims, inferred from command structure size

for i, itemkey in enumerate(inputdata):
    Qitem = pywikibot.ItemPage(repo, itemkey)
    Qitem.get()
    if not Qitem.claims: # item does not have any claims
        continue
    
    moved_claims = action_moveP(Qitem, claimProperties)
    if True or (len(inputdata)-i-1)%100==0:
        print('{} (UTC), {}: mv {:d} pr:{} -> pr:{}; {:d}/{:d} item(s) to go'.format(time.strftime(timestampformat), Qitem.title(), moved_claims, fromRefProperty, toRefProperty, len(inputdata)-i-1, len(inputdata)))
        
print('all done, job finished')
Found 1 items to process
2019-01-01 19:55:13 (UTC), Q457515: mv 1 pr:P248 -> pr:P248; 0/1 item(s) to go
all done, job finished