import os
import json
import argparse
import sys
import numpy as np

sys.path.insert(0, 'src/')

from tqdm import tqdm
from typing import Dict, Any

from logger_config import logger
from data_utils import load_query_answers, load_corpus, save_to_readable_format

parser = argparse.ArgumentParser(description='data preprocessing for NQ & TriviaQA in DPR paper')
parser.add_argument('--out-dir', default='./data/dpr/', type=str, metavar='N',
                    help='output directory')
parser.add_argument('--task', default='nq', type=str, metavar='N',
                    help='task name, nq or tq')
parser.add_argument('--train-pred-path', default='amlt/0621_cont_100k_psg16_ft/nq/nq_train.dpr.json',
                    type=str, metavar='N', help='path to train predictions to construct negatives')
parser.add_argument('--dev-pred-path', default='amlt/0621_cont_100k_psg16_ft/nq/nq_dev.dpr.json',
                    type=str, metavar='N', help='path to dev predictions to construct negatives')
parser.add_argument('--num-negatives', default=100, type=int, metavar='N',
                    help='number of negative passages')
parser.add_argument('--depth', default=100, type=int, metavar='N',
                    help='depth to choose negative passages from')

args = parser.parse_args()
os.makedirs(args.out_dir, exist_ok=True)
logger.info('Args: {}'.format(json.dumps(args.__dict__, ensure_ascii=False, indent=4)))
assert args.task in ['nq', 'tq']


def _load_qid_to_positive(path: str) -> Dict[str, str]:
    if args.task != 'nq':
        logger.warning('Only NQ has manually labeled positives')
        return {}

    examples = json.load(open(path, 'r', encoding='utf-8'))
    qid_to_pos_id = {}
    for ex in examples:
        positive_ctxs = ex['positive_ctxs']
        if len(positive_ctxs) > 0:
            qid_to_pos_id[ex['question']] = str(int(positive_ctxs[0]['passage_id']) - 1)

    logger.info('Get {} manually labeled positives from {}'.format(len(qid_to_pos_id), path))
    return qid_to_pos_id


def _write_prepared_data_to_disk(out_path: str,
                                 split: str,
                                 queries: Dict[str, Dict[str, Any]],
                                 preds_path: str):
    qid_to_pos_id = _load_qid_to_positive(path='{}/biencoder-nq-{}.json'.format(args.out_dir, split))
    cnt_filtered = 0

    preds = json.load(open(preds_path, 'r', encoding='utf-8'))

    with open(out_path, 'w', encoding='utf-8') as writer:
        for query_id in tqdm(queries, mininterval=1, desc='prepare {} data'.format(split)):
            cur_pred: dict = preds[query_id] if query_id in preds else preds[query_id.strip()]

            positive_ids, negative_ids = [], []
            manual_positive_id = qid_to_pos_id.get(query_id, None)
            if manual_positive_id:
                positive_ids.append(manual_positive_id)

            for ctx in cur_pred['contexts'][:args.depth]:
                doc_id = str(ctx['docid'])
                if doc_id == manual_positive_id:
                    continue
                elif ctx['has_answer']:
                    positive_ids.append(doc_id)
                else:
                    negative_ids.append(doc_id)

            if not positive_ids or not negative_ids:
                cnt_filtered += 1
                continue

            np.random.shuffle(negative_ids)
            negative_ids = negative_ids[:args.num_negatives]

            doc_id_to_score = {str(ctx['docid']): float(ctx['score']) for ctx in cur_pred['contexts']}
            doc_id_to_score[manual_positive_id] = 1000.
            example = {
                'query_id': query_id,
                'query': queries[query_id]['query'],
                'answers': queries[query_id]['answers'],
                'positives': {'doc_id': positive_ids,
                              'score': [doc_id_to_score.get(doc_id, -1.) for doc_id in positive_ids]
                              },
                'negatives': {'doc_id': negative_ids,
                              'score': [doc_id_to_score.get(doc_id, -1.) for doc_id in negative_ids]
                              },
            }

            writer.write(json.dumps(example, ensure_ascii=False, separators=(',', ':')))
            writer.write('\n')

    if cnt_filtered > 0:
        logger.info('{} questions are filtered out'.format(cnt_filtered))

    logger.info('Done write {} data to {}'.format(split, out_path))


if __name__ == '__main__':
    for split in ['dev', 'train']:
        _write_prepared_data_to_disk(
            out_path=os.path.join(args.out_dir, '{}_hard_{}.jsonl'.format(args.task, split)),
            split=split,
            queries=load_query_answers(path=os.path.join(args.out_dir, '{}_{}_queries.tsv'.format(args.task, split))),
            preds_path=(args.train_pred_path if split == 'train' else args.dev_pred_path)
        )

    corpus = load_corpus(path=os.path.join(args.out_dir, 'passages.jsonl.gz'))
    for split in ['dev', 'train']:
        save_to_readable_format(in_path=os.path.join(args.out_dir, '{}_hard_{}.jsonl'.format(args.task, split)),
                                corpus=corpus)

    logger.info('Done')
