# use API
import requests
import pandas as pd

import os
import sys
import logging
import urllib
import datetime as dt
import json
import csv
from pathlib import Path
from datetime import date
from datetime import timedelta
import datetime
import zipfile
import time
import shutil
import metadata_parsing_functions

from selenium import webdriver
from chromedriver_py import binary_path
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.common.exceptions import WebDriverException
from bs4 import BeautifulSoup

sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'processing_modules'))
import GHOST_standards


class eea_ereporting_api(object):
    """defines and configures API to query the eea database"""

    def __init__(self, ):
        self.logger = logging.getLogger(__file__)
        self.url_api = "https://eeadmz1-downloads-api-appservice.azurewebsites.net/"
        self.headers = {'content-type': 'application/json', 'accept': 'application/json'}

        self.parquet_file_urls = 'ParquetFile/urls'
        self.parquet_files = 'ParquetFile'
        self.get_countries = 'Country'
        self.get_cities = 'City'
        self.components = 'Pollutant'

        return

    def get_parquet_files(self, mode, max_time_per_dl, n_max_tries, pcode, dataset, start_time, end_time, aggregation_type):
        """build a query to return data in parquet files"""
        query = urllib.parse.urljoin(self.url_api, self.parquet_files)
        if mode == 'all':
            request_body = {'countries': [], 'cities': [], 'pollutants': [pcode], 'dataset':dataset, 'source': 'Api', 'aggregationType': aggregation_type}
        elif mode == 'nrt':
            request_body = {'countries': [], 'cities': [], 'pollutants': [pcode], 'dataset':dataset, 'source': 'Api', 'dateTimeStart': start_time, 'dateTimeEnd': end_time, 'aggregationType': aggregation_type}

        response = requests.post(url=query, json=request_body, timeout=max_time_per_dl)

        if response.status_code == 200:
            return response.content
        else:
            print('Failed downloading {}_{}_{} in {} seconds, response error code {}'.format(ccode, pcode, dataset, max_time_per_dl, response.status_code))
            return None
    
    def get_parquet_file_urls(self, mode, max_time_per_dl, n_max_tries, ccode, pcode, dataset, start_time, end_time, aggregation_type):
        """build a query to return data in parquet files"""
        query = urllib.parse.urljoin(self.url_api, self.parquet_file_urls)
        if mode == 'all':
            request_body = {'countries': [], 'cities': [], 'pollutants': [pcode], 'dataset':dataset, 'source': 'Api', 'aggregationType': aggregation_type}
        elif mode == 'nrt':
            request_body = {'countries': [], 'cities': [], 'pollutants': [pcode], 'dataset':dataset, 'source': 'Api', 'dateTimeStart': start_time, 'dateTimeEnd': end_time, 'aggregationType': aggregation_type}

        response = requests.post(url=query, json=request_body, timeout=max_time_per_dl)

        if response.status_code == 200:
            return response.text
        else:
            print('Failed downloading {}_{}_{} in {} seconds, response error code {}'.format(ccode, pcode, dataset, max_time_per_dl, response.status_code))
            return None
        

    def get_countrycodes_in_list(self, ):
        """build a query to return country codes"""
        query = urllib.parse.urljoin(self.url_api, self.get_countries)
        response = requests.get(url=query, data='')
        if response.status_code == 200:
            return response.text
        else:
            return print('Response error {}'.format(response.status_code))

    def get_components(self, ):
        """build a query to return components"""
        query = urllib.parse.urljoin(self.url_api, self.components)
        response = requests.get(url=query, data='')
        if response.status_code == 200:
            return response.text
        else:
            return print('Response error {}'.format(response.status_code))

    def get_cities_in_list(self, ccode):
        """build a query to return cities"""
        query = urllib.parse.urljoin(self.url_api, self.get_cities)
        request_body = [ccode]
        response = requests.post(url=query, json=request_body)
        if response.status_code == 200:
            return response.content
        else:
            return print('Response error {}'.format(response.status_code))


def download_data(mode, version, n_max_tries, max_time_per_dl, start, stop, dl_root):
    # from October 2024 the API will have option to choose desired time period; until then, we will use https://discomap.eea.europa.eu/map/fme/AirQualityUTDExport.htm for nrt

    Headers = {'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148'}

    if mode == 'all':
        streams = ['E1a', 'E2a', 'Airbase'] 
        start_time = '' # not submitted in request
        end_time = ''

    elif mode == 'nrt':
        version = mode
        streams = ['E2a'] # this means E2a: data from 2023-01-01 until present
        #start_time = '2013-01-01T00:00:00.000Z'
        #end_time = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f'+'Z') #'2024-10-01T14:39:57.969Z'
        end_time = stop.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
        start_time = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        print(start_time)
        print(end_time)

    else:
        print('time mode inapplicable')

    # time resolution
    aggregation_types = ['hour', 'day', 'var']

    #if mode == 'all': # for now API not used for nrt; from October 2024 it will be 

    # get countries
    countries = json.loads(eea_ereporting_api().get_countrycodes_in_list())
    #countries = [{'countryCode': 'AT'}]

    # get all components available
    components = json.loads(eea_ereporting_api().get_components())

    # get relevant component codes from GHOST standards for components to download
    GHOST_standard_components = GHOST_standards.standard_parameters
    relevant_component_codes = {}
    for GHOST_standard_component in GHOST_standard_components:
        if 'PREC' in GHOST_standard_component: # WET DEP COMPONENTS
            relevant_component_codes[GHOST_standard_components[GHOST_standard_component]['airbase_code'].lstrip('0')] = GHOST_standard_components[GHOST_standard_component]['matrix']

        # all
        #relevant_component_codes[GHOST_standard_components[GHOST_standard_component]['airbase_code'].lstrip('0')] = GHOST_standard_components[GHOST_standard_component]['matrix']

    # get GHOST relevant components
    relevant_components = []
    for component in components:
        ID = component['id'].split('/')[-1]
        if ID in list(relevant_component_codes.keys()):
            component['matrix'] = relevant_component_codes[ID]
            relevant_components.append(component)

    print(relevant_components)

    # START DOWNLOAD
    for stream in streams: 
        # choose the dataset
        if stream == 'E1a':
            dataset = 2 # this means E1a: data from 2013 until 2022
        elif stream == 'E2a':
            dataset = 1 # this means E2a: data from 2023-01-01 until present
        elif stream == 'Airbase':
            dataset = 3 # this means Airbase, old data until 2013
        else:
            print("Invalid data stream")
            continue
        print("Download started from {}".format(stream))

        #for country in countries:
        ccode = [] # all countries; country['countryCode']

        for aggregation_type in aggregation_types:

            for component in relevant_components: 
                component_link = component['id'] # is unique -> pass to API
                component_code = component['id'].split('/')[-1] # is unique
                component_name = component['notation'].replace(' ', '').replace('=', '').replace('(', '').replace(')', '') # is not unique in general, but unique per matrix
                matrix = component['matrix']
                
                if mode == 'all':
                    # download links to parquet files first to get maximal temporal range. Then download parquet files
                    urls_raw = eea_ereporting_api().get_parquet_file_urls(mode, max_time_per_dl, n_max_tries, ccode, component_link, dataset, start_time, end_time, aggregation_type)

                    if str(urls_raw).strip() == 'ParquetFileUrl':
                        continue # move to next component
                    else:
                        url = str(urls_raw.strip().split('FileUrl')[-1].replace('\r\n', ''))
                        urls = url.split('.parquet')
                        
                        for url in urls: # one url corresponds to one sample point
                            if url != '':
                                url = url+'.parquet'

                                # country code
                                country = url.split('/')[-2]

                                # download directory
                                sampling_point_ID = url.split('/')[-1][:-8]
                                os.makedirs('{}/EEA_AQ_eReporting/original_files/{}/{}/{}/{}/{}/'.format(dl_root,version, stream, matrix, component_name, country), exist_ok=True)
                                download_location = '{}/EEA_AQ_eReporting/original_files/{}/{}/{}/{}/{}/'.format(dl_root,version, stream, matrix, component_name,country)
                                filename = '{}_{}.parquet'.format(sampling_point_ID, aggregation_type)

                                # download file
                                r = requests.get(url, headers=Headers, timeout=max_time_per_dl)
                                if r.status_code == 200:
                                    with open(download_location+filename, 'wb') as outfile:
                                        outfile.write(r.content)
                                    print("Downloaded {} {} {} {} {}".format(stream, matrix, component_name, country, filename))

                                # convert
                                for file in os.listdir(download_location):
                                    if file.endswith(".parquet"):
                                        df = pd.read_parquet(download_location+file)
                                        df.to_csv(download_location+'/{}.csv'.format(file[:-8]), index=None)
                                        os.remove(download_location+file)


                elif mode == 'nrt':
                    # download parquet files directly. One request per component and aggregation type, all countries and stations
                    data = eea_ereporting_api().get_parquet_files(mode, max_time_per_dl, n_max_tries, component_link, dataset, start_time, end_time, aggregation_type)
                    if data == None:
                        continue # move to next component
                    else:
                        os.makedirs('{}/EEA_AQ_eReporting/original_files/{}/{}/{}/{}/'.format(dl_root,version, stream, matrix, component_name), exist_ok=True)
                        download_location = '{}/EEA_AQ_eReporting/original_files/{}/{}/{}/{}/'.format(dl_root,version, stream, matrix, component_name)
                        filename = '{}_{}.zip'.format(aggregation_type, component_code)

                        with open(download_location+filename, 'wb') as outfile:
                            outfile.write(data)
                        print("Downloaded {}".format(filename))

                        zip_file_empty = False
                        with zipfile.ZipFile(download_location+filename, 'r') as zip_ref:
                            if len(zip_ref.namelist()) > 0: # check if zip is empty
                                zip_ref.extractall(download_location)
                            else: # zip file is empty, delete directory
                                zip_file_empty = True
                        
                        os.remove(download_location+filename)
                        if zip_file_empty == True: # stop processing
                            shutil.rmtree('{}/EEA_AQ_eReporting/original_files/{}/{}/{}/{}/'.format(dl_root,version, stream, matrix, component_name))
                            continue

                        # list all parquetfiles
                        parquets = os.listdir(download_location+'{}/'.format(stream))
                        for parquet in parquets:
                            sampling_point_ID = parquet.split('/')[-1][:-8]
                            df = pd.read_parquet(download_location+'/{}/{}'.format(stream, parquet))
                            df.to_csv(download_location+'/{}_{}.csv'.format(sampling_point_ID, aggregation_type), index=None)
                        shutil.rmtree(download_location+'/{}'.format(stream))
                        print("Conversion to csv done!")


    # elif mode == 'nrt':
    #     # download last 48 hours of available data from https://discomap.eea.europa.eu/map/fme/AirQualityUTDExport.htm
    #     #Python sample for download of air quality up-to-date files
    #     #The Pyton script shows a simple sample how the pre-processed CSV files can be downloaded.
    #     #EEA takes no responsibility of the script and the code is provided 'as is', without warranty of any kind.
    #     today = date.today().strftime('%Y%m%d')
    #     os.makedirs('{}/EEA_AQ_eReporting/original_files/nrt/discomap/{}/'.format(dl_root,today), exist_ok=True)

    #     # Set download url (updated 02/05/2024)
    #     ServiceUrl = "https://eeadmz1batchservice02.blob.core.windows.net/airquality-e-extract"

    #     inventory_url = ' https://eeadmz1batchservice02.blob.core.windows.net/airquality-e-extract/Inventory.csv'
    #     n_tries = 0
    #     errcode = 999

    #     while (n_tries < n_max_tries) and (errcode != 200):
    #         r = requests.get(inventory_url, timeout=max_time_per_dl)
    #         if r.status_code == 200:
    #             urllib.request.urlretrieve(inventory_url, '{}/EEA_AQ_eReporting/original_files/nrt/inventory_disco.csv'.format(dl_root))
    #             errcode = r.status_code

    #         elif r.status_code == 404:
    #             print('No {}'.format(inventory_url))
    #             errcode = 200 #skip
    #         else:
    #             # try again
    #             print('Response error {}, attempt {}'.format(r.status_code, n_tries))
    #             errcode = r.status_code
    #             n_tries += 1
    #             time.sleep(n_tries ** 2) # wait a lil more every time 

    #     if n_tries == n_max_tries:
    #         print('Failed downloading {} {} times in {} seconds, error code {}'.format(inventory_url, n_tries, max_time_per_dl, errcode))

    #     inventory = pd.read_csv( '{}/EEA_AQ_eReporting/original_files/nrt/inventory_disco.csv'.format(dl_root), header=0)

    #     # Countries to download
    #     # Note: List is not complete
    #     #countries = ['SE','DE','ES']

    #     # Pollutant to be downloaded
    #     #pollutants = ['C6H6','PM10','CO','NO2','SO2','O3','PM2.5']

    #     for index, row in inventory.iterrows():
    #         country = row['country']
    #         pollutant = row['pollutant'].replace(' ', '')

    #         fileName = '{}/EEA_AQ_eReporting/original_files/nrt/discomap/{}/{}_{}.csv'.format(dl_root,today, country, pollutant)
    #         downloadFile = '%s/%s_%s.csv' % (ServiceUrl, country, pollutant)
    #         n_tries = 0
    #         errcode = 999

    #         while (n_tries < n_max_tries) and (errcode != 200):
    #             r = requests.get(downloadFile, timeout=max_time_per_dl)
    #             if r.status_code == 200:
    #                 urllib.request.urlretrieve(downloadFile, fileName)
    #                 print("Downloaded {}".format(downloadFile))
    #                 errcode = r.status_code

    #             elif r.status_code == 404:
    #                 print('No {}'.format(downloadFile))
    #                 errcode = 200 #skip
    #             else:
    #                 # try again
    #                 print('Response error {}, attempt {}'.format(r.status_code, n_tries))
    #                 errcode = r.status_code
    #                 n_tries += 1
    #                 time.sleep(n_tries ** 2) # wait a lil more every time 

    #         if n_tries == n_max_tries:
    #             print('Failed downloading {} {} times in {} seconds, error code {}'.format(downloadFile, n_tries, max_time_per_dl, errcode))

    #             # print('Downloading: %s' % downloadFile )
    #             # file = requests.get(downloadFile).content
    #             # with open(fileName, 'wb') as output:
    #             #     output.write(file)
                


def download_metadata(n_max_tries, max_time_per_dl, version, dl_root,  meta_basefile, meta_history, meta_params):

    baseurl = 'https://discomap.eea.europa.eu/App/AQViewer/index.html?fqn=Airquality_Dissem.b2g.measurements'
    os.makedirs('{}/EEA_AQ_eReporting/metadata/{}/network_provided/temp/'.format(dl_root,version), exist_ok=True)
    download_location = '{}/EEA_AQ_eReporting/metadata/{}/network_provided/temp/'.format(dl_root,version)
    today = date.today()
    
    n_tries = 0
    errcode = 999
    metadata_new = {}
    # while (n_tries < n_max_tries) and (errcode != 200):
    #     try:
    #         # set up driver
    #         options = Options()
    #         prefs = {'download.default_directory' : download_location}
    #         options.add_experimental_option('prefs', prefs)
    #         options.add_argument("--no-sandbox")
    #         options.add_argument("--headless")
    #         svc = webdriver.ChromeService(executable_path=binary_path)
    #         driver = webdriver.Chrome(service=svc, options=options)

    #         # open url
    #         driver.get(baseurl)
    #         #WebDriverWait(driver, max_time_per_dl).until(EC.presence_of_element_located((By.XPATH, '//span[@class="nomobile"][text()="Download CSV"]/..'))) # wait till loaded
    #         time.sleep(10)

    #         button = driver.find_element(By.XPATH, '//span[@class="nomobile"][text()="Download CSV"]/..') # find parent of span element "Download CSV"
    #         button.click() #download

    #         while not os.path.exists(download_location+'DataExtract.csv.zip'):
    #             time.sleep(1)

    #             if os.path.isfile(download_location+'DataExtract.csv.zip'):
    #                 print('metadata download successful')

    #                 # unzip
    #                 with zipfile.ZipFile(download_location+'DataExtract.csv.zip', 'r') as zip_ref:
    #                     zip_ref.extractall(download_location)
            
    #         os.remove(download_location+'DataExtract.csv.zip')
    #         os.rename(download_location+'DataExtract.csv', download_location+'EEA_AQ_eReporting_META_{}.csv'.format(today.strftime('%Y%m%d')))

    #         driver.close()
    #         errcode = 200
    
    #     except WebDriverException as e:
    #         print(e) 
    #         n_tries = n_tries+1
    #         print("Number of tries: {}".format(n_tries))
    #         continue

    # if n_tries == n_max_tries:
    #     print('Failed downloading EEA_AQ_eReporting metadata {} times in {} seconds'.format(n_tries, max_time_per_dl))

    # print('done')

    # split downloaded file in csv files by state
    os.makedirs('{}/EEA_AQ_eReporting/metadata/{}/network_provided/EEA_AQ_eReporting_META_{}/'.format(dl_root, version, today.strftime('%Y%m%d')), exist_ok=True)
    #os.makedirs('{}/EEA_AQ_eReporting/metadata/{}/processed/'.format(dl_root, version, filenames[i][4:-4]), exist_ok=True)

    with open(download_location+'EEA_AQ_eReporting_META_{}.csv'.format(today.strftime('%Y%m%d')), 'r') as file:
        csv_filedata = csv.DictReader(file, delimiter=',')

        countries = []
        [countries.append(row['Air Quality Station EoI Code'][:2]) for row in csv_filedata if row['Air Quality Station EoI Code'][:2] not in countries]
    
        for country in countries:
            print(country)
            file.seek(0)
            with open("{}/EEA_AQ_eReporting/metadata/{}/network_provided/EEA_AQ_eReporting_META_{}/EEA_AQ_eReporting_{}_META_{}.csv".format(dl_root, version, today.strftime('%Y%m%d'), country, today.strftime('%Y%m%d')), "w") as f_out:
                writer = csv.DictWriter(f_out, fieldnames=csv_filedata.fieldnames, delimiter=",")
                writer.writeheader()
                for row in csv_filedata:
                    if row['Air Quality Station EoI Code'][:2] == country:
                        writer.writerow(row)

    # JSON, process per state  ==========================================================================================================
    network = os.path.basename(__file__).split('_downloa')[0]
    present_meta_file_id = today.strftime('%Y%m%d')

    for state_code in countries:

        if meta_basefile == True:  # create json from base metadata file
            network_provided_meta_file = '{}/{}/metadata/{}/network_provided/EEA_AQ_eReporting_META_{}/{}_{}_META_{}.csv'.format(dl_root, network, version, meta_params['base_file_id'], network, state_code, meta_params['base_file_id'])
            json_base_file = metadata_parsing_functions.create_json_file(dl_root, network, version, network_provided_meta_file, meta_params)

            # safe base file
            with open('{}/{}/metadata/{}/processed/{}_{}_META.json'.format(dl_root, network, version, network, state_code), 'w', encoding=meta_params['encoding']) as f:
                f.write(json.dumps(json_base_file, indent=4))

        else:
            pass

        if meta_history == True: # parse all ever downloaded meta files
            print('Meta history parsing not implemented yet')
            pass
        #     # list all meta files
        #     meta_dir = '{}/{}/metadata/{}/network_provided/'.format(dl_root, network, version)
        #     network_provided_meta_files = [meta_dir+f for f in os.listdir(meta_dir) if (('META_' in f) and ('.csv' in f))]
        #     network_provided_meta_files = sorted(network_provided_meta_files)  # sort in right order
            
        #     for network_provided_meta_file in network_provided_meta_files: # loop through all files
        #         json_metadata_now = create_json_file(dl_root, network, version, network_provided_meta_file, meta_params)

        #         # compare current file with json basefile and update it if necessary
        #         compare_jsons(dl_root, network, version, json_metadata_now, meta_params)


        else: # only parse latest downloaded meta file
            #create json from current metadata file
            network_provided_present_meta_file = '{}/{}/metadata/{}/network_provided/EEA_AQ_eReporting_META_{}/{}_{}_META_{}.csv'.format(dl_root, network, version, present_meta_file_id, network, state_code, present_meta_file_id)
            json_metadata_now = metadata_parsing_functions.create_json_file(dl_root, network, version, network_provided_present_meta_file, meta_params)

            # compare current file with json basefile and update it if necessary
            # modify 'network' variable to contain information about state and monitors/state, to find correct basefile in comparing-json-function
            network = network+'-'+state_code
            print(network)
            metadata_parsing_functions.compare_jsons(dl_root, network, version, json_metadata_now, meta_params)
            network = network.split('-')[0]


    # i=i+1

    # # JSON =====================================================================
    # network = os.path.basename(__file__).split('_downloa')[0]
    # present_meta_file_id = today.strftime('%Y%m%d')

    # metadata_parsing_functions.json_parser(dl_root, network, version, meta_basefile, meta_history, meta_params, present_meta_file_id)

    """
    # create json from original metadata file =====================================================================================
    json_metadata = {}
    with open('{}/EEA_AQ_eReporting/metadata/{}/network_provided/DataExtract.csv'.format(dl_root,version), 'r') as file:
        csv_filedata = csv.DictReader(file)

        for row in csv_filedata:  
            key = row['Air Quality Station EoI Code']+'_'+row['Sampling Point Id']+'_'+row['Process Id']
            update_date = today.strftime('%Y-%m-%d')
            for parameter in row:
                row[parameter] = {'values': [row[parameter]], 'update_time': [update_date]} # create inner dictionary for every parameter
            json_metadata[key] = row

    with open('{}/EEA_AQ_eReporting/metadata/{}/processed/EEA_AQ_eReporting_META.json'.format(dl_root,version), 'w', encoding='utf-8') as f:
        f.write(json.dumps(json_metadata, indent=4, ensure_ascii=False))
    
    
    # create json in desired shape from current metadata file
    json_metadata_now = {}  
    with open(download_location+"DataExtract.csv", encoding='utf-8') as file:
        csv_filedata = csv.DictReader(file)
        print('hola')
        for row in csv_filedata:  
            key = row['Air Quality Station EoI Code']+'_'+row['Sampling Point Id']+'_'+row['Process Id']
            update_date = today.strftime('%Y-%m-%d')
            for parameter in row:
                row[parameter] = {'values': [row[parameter]], 'update_time': [update_date]} # create inner dictionary for every parameter
            json_metadata_now[key] = row
        print('hola2')
    
    with open('{}/EEA_AQ_eReporting/metadata/{}/network_provided/EEA_AQ_eReporting_META_{}.json'.format(dl_root,version, today.strftime('%Y%m%d')), 'w', encoding='utf-8') as f:
        f.write(json.dumps(json_metadata_now, indent=4, ensure_ascii=False))
        

    # read standardised file to compare!
    with open('{}/EEA_AQ_eReporting/metadata/{}/processed/EEA_AQ_eReporting_META.json'.format(dl_root,version), 'r', encoding='utf-8') as f:
        json_metadata = json.loads(f.read())

        for station in json_metadata: # loop through all the old stations
            if station in json_metadata_now.keys(): # if station is in current meta data, go on
                for parameter in json_metadata[station]:
                    if json_metadata[station][parameter]['values'][-1] != json_metadata_now[station][parameter]['values'][0]: # compare last entry in standardised file to value in new file
                        # if different value, append the standardised metadeta file
                        print("old {} --- new {}".format(json_metadata[station][parameter]['values'][-1], json_metadata_now[station][parameter]['values'][0]))
                        json_metadata[station][parameter]['values'].append(json_metadata_now[station][parameter]['values'][0])
                        json_metadata[station][parameter]['update_time'].append(json_metadata_now[station][parameter]['update_time'][0])
                    else:
                        pass
            else:
                print('Station {} was abolished'.format(station))

        for station in json_metadata_now: # loop through all the new stations
            if station in json_metadata.keys(): # if station is in old meta data
                pass # comparison was done before
            else: # new station appeared!
                print('New station {}'.format(station))
                json_metadata.update({station: json_metadata_now[station]})

    # safe
    with open('{}/EEA_AQ_eReporting/metadata/{}/processed/EEA_AQ_eReporting_META.json'.format(dl_root,version), 'w', encoding='utf-8') as f:
        f.write(json.dumps(json_metadata, indent=4, ensure_ascii=False))"""
        
