__all__ = ['importer']

import json
from glob import glob
import importlib
from itertools import islice
from pathlib import Path
import sys
from zipfile import ZipFile
from dpath import get
from tqdm import tqdm

from corylus import array, time, xml

from .geo import geo, geo2tile
from .db import get_session_factory

from .models import (
    BagText,
    BagNum,
    BagOpr,
    BagPnd,
    BagSta,
    BagLig,
    BagVbo,
    BagVboPnd,
    BagVboPurpose,
    BagWpl,
    BagWplGem,
)

def importer(db, collections=None, test=False):
    """
    Import BAG data into an SQLite database using SQLAlchemy ORM.

    `db` is a path or an SQLAlchemy URL.
    """
    Session = get_session_factory(db)

    if not collections:
        collections = [
            'text', 'wpl', 'wpl_gem', 'opr', 'num', 'vbo',
            'vbo_purpose', 'pnd', 'vbo_pnd', 'sta', 'lig',
        ]
    if isinstance(collections, str):
        collections = collections.split(',')

    # Map collection name to (model, producer)
    collection_map = {
        'text': (BagText, text_rows),
        'num': (BagNum, num_rows),
        'vbo': (BagVbo, vbo_rows),
        'vbo_purpose': (BagVboPurpose, vbo_purpose_rows),
        'pnd': (BagPnd, pnd_rows),
        'vbo_pnd': (BagVboPnd, vbo_pnd_rows),
        'sta': (BagSta, sta_rows),
        'lig': (BagLig, lig_rows),
        'opr': (BagOpr, opr_rows),
        'wpl': (BagWpl, wpl_rows),
        'wpl_gem': (BagWplGem, wpl_gem_rows),
    }

    for collection in collections:
        if collection not in collection_map:
            print(f"Unknown collection: {collection}", file=sys.stderr)
            continue

        model, row_func = collection_map[collection]
        print(f"-- {collection}", file=sys.stderr)

        stream = read(collection)
        rows_iter = row_func(stream)

        if test:
            rows_iter = islice(rows_iter, 10 if isinstance(test, bool) else test)

        # Use a separate session per collection
        session = Session()
        try:
            # Progress bar wrapping the generator
            session.bulk_insert_mappings(
                model,
                tqdm(rows_iter, miniters=1_000),
            )
            session.commit()
        except Exception:
            session.rollback()
            raise
        finally:
            session.close()


def num_rows(stream):
    for data in stream:
        yield {
            'id': int(data['identificatie']['_']),
            'postcode': data.get('postcode'),
            'number': int(data['huisnummer']),
            'extra': data.get('huisnummertoevoeging'),
            'letter': data.get('huisletter'),
            'type': data['typeAdresseerbaarObject'][0:1],
            'opr': int(get(data, ('ligtAan', 'OpenbareRuimteRef', '_'))),
            'date': jd(data),
        }


def vbo_rows(stream):
    for data in stream:
        g = geo(data['geometrie'])
        yield {
            'id': int(data['identificatie']['_']),
            'num': int(get(data, ('heeftAlsHoofdadres', 'NummeraanduidingRef', '_'))),
            'geo': g,
            'area': int(data['oppervlakte']),
            'tile': geo2tile(g),
            'date': jd(data),
        }


def vbo_purpose_rows(stream):
    for data in stream:
        for purpose in array(data['gebruiksdoel']):
            yield {
                'vbo': int(data['identificatie']['_']),
                'purpose': TEXT[purpose],
            }


def pnd_rows(stream):
    for data in stream:
        yield {
            'id': int(data['identificatie']['_']),
            'geo': geo(data['geometrie']),
            'year': int(data['oorspronkelijkBouwjaar']),
            'status': TEXT[data['status']],
            'date': jd(data),
        }


def vbo_pnd_rows(stream):
    for data in stream:
        for pand in array(get(data, ('maaktDeelUitVan', 'PandRef'))):
            yield {
                'vbo': int(data['identificatie']['_']),
                'pnd': int(pand['_']),
            }


def sta_rows(stream):
    for data in stream:
        g = geo(data['geometrie'])
        yield {
            'id': int(data['identificatie']['_']),
            'num': int(get(data, ('heeftAlsHoofdadres', 'NummeraanduidingRef', '_'))),
            'status': TEXT[data['status']],
            'geo': g,
            'tile': geo2tile(g),
            'date': jd(data),
        }


def lig_rows(stream):
    # Same mapping as sta_rows
    for row in sta_rows(stream):
        yield row


def opr_rows(stream):
    for data in stream:
        yield {
            'id': int(data['identificatie']['_']),
            'name': data['naam'],
            'type': TEXT[data['type']],
            'status': TEXT[data['status']],
            'wpl': int(get(data, ('ligtIn', 'WoonplaatsRef', '_'))),
            'date': jd(data),
        }


def wpl_rows(stream):
    for data in stream:
        if data['status'] != 'Woonplaats aangewezen':
            continue
        g = geo(data['geometrie'])
        yield {
            'id': int(data['identificatie']['_']),
            'name': data['naam'],
            'geo': g,
            'tile': geo2tile(g, granularity=1),
            'date': jd(data),
        }


def wpl_gem_rows(stream):
    for data in stream:
        yield {
            'wpl': int(get(data, ('gerelateerdeWoonplaats', 'identificatie'))),
            'gem': int(get(data, ('gerelateerdeGemeente', 'identificatie'))),
        }


def text_rows(_stream):
    for text_value, id_value in TEXT.items():
        yield {
            'text': text_value,
            'id': id_value,
        }


def read(collection):
    match collection:
        case 'text':
            return
        case 'wpl_gem':
            filename = glob('GEM-WPL*')[0]
        case _:
            filename = glob(f"9999*{collection.split('_')[0].upper()}*")[0]

    with ZipFile(filename, 'r') as zipfile:
        for name in zipfile.namelist():
            with zipfile.open(name) as file:
                if collection == 'wpl_gem':
                    for data in xml.parse(file, level=4):
                        if not data:
                            continue
                        if get(
                            data,
                            ('tijdvakgeldigheid', 'einddatumTijdvakGeldigheid'),
                            default=None,
                        ):
                            continue
                        yield data
                else:
                    for data in xml.parse(file, level=3):
                        if not data:
                            continue
                        if not (data := data.get('bagObject')):
                            continue
                        data = data[next(iter(data))]
                        if get(
                            data,
                            ('voorkomen', 'Voorkomen', 'eindGeldigheid'),
                            default=None,
                        ):
                            continue
                        yield data


def jd(data):
    return time.julian_day(
        get(data, ('voorkomen', 'Voorkomen', 'beginGeldigheid'))
    )


TEXT = {
    'Bouw gestart': 1,
    'Bouwvergunning verleend': 2,
    'Niet gerealiseerd pand': 3,
    'Pand buiten gebruik': 4,
    'Pand gesloopt': 5,
    'Pand in gebruik': 6,
    'Pand in gebruik (niet ingemeten)': 7,
    'Pand ten onrechte opgevoerd': 8,
    'Sloopvergunning verleend': 9,
    'Verbouwing pand': 10,
    'Administratief gebied': 21,
    'Kunstwerk': 22,
    'Landschappelijk gebied': 23,
    'Spoorbaan': 24,
    'Terrein': 25,
    'Water': 26,
    'Weg': 27,
    'Naamgeving ingetrokken': 41,
    'Naamgeving uitgegeven': 42,
    'Plaats aangewezen': 61,
    'Plaats ingetrokken': 62,
    'bijeenkomstfunctie': 81,
    'celfunctie': 82,
    'gezondheidszorgfunctie': 83,
    'industriefunctie': 84,
    'kantoorfunctie': 85,
    'logiesfunctie': 86,
    'onderwijsfunctie': 87,
    'overige gebruiksfunctie': 88,
    'sportfunctie': 89,
    'winkelfunctie': 90,
    'woonfunctie': 91,
}
