# -*- coding: utf-8 -*-
#
# Copyright 2016 dpa-infocom GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
from datetime import datetime
from sqlalchemy_aio import ASYNCIO_STRATEGY
from sqlalchemy import create_engine, MetaData, Table, Column,\
                       Integer, String, Text, Boolean, DateTime, create_engine
from sqlalchemy.schema import CreateTable
from sqlalchemy.sql import select
from livebridge.storages.base import BaseStorage


logger = logging.getLogger(__name__)

"""
http://docs.sqlalchemy.org/en/latest/core/engines.html
"""
class SQLStorage(BaseStorage):
    
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SQLStorage, cls).__new__(cls)
            logger.debug("SQL client: {}".format(cls._instance))
        return cls._instance

    def __init__(self, **kwargs):
        self.dsn = kwargs.get("dsn", None)
        self.table_name = kwargs.get("table_name")
        
    @property
    async def db(self):
        if hasattr(self, "_engine") and self._engine:
            return self._engine
        logger.debug("Connecting to {}".format(self.dsn))
        self._engine = create_engine(self.dsn, strategy=ASYNCIO_STRATEGY)
        return self._engine

    def _get_table(self):
        return Table(self.table_name, MetaData(),
            Column("id", Integer(), primary_key=True),
            Column("target_id", String(150), index=True),
            Column("post_id", String(150), index=True),
            Column("source_id", String(150), index=True),
            Column("sticky", Boolean()),
            Column("text", Text()),
            Column("created", DateTime()),
            Column("updated", DateTime(), index=True),
            Column("target_doc", Text()),
        )

    async def setup(self):
        """Setting up SQL table, if it not exists."""
        try:
            engine = await self.db
            if not await engine.has_table(self.table_name):
                # create table
                logger.info("Creating SQL table [{}]".format(self.table_name))
                items = self._get_table()
                await engine.execute(CreateTable(items))
                # create indeces
                conn = await engine.connect()
                await conn.execute("CREATE INDEX `lb_last_updated` ON `{}` (`source_id` DESC,`updated` DESC);".format(self.table_name))
                await conn.execute("CREATE INDEX `lb_post` ON `{}` (`target_id` DESC,`post_id` DESC);".format(self.table_name))
                await conn.close()
                return True
        except Exception as e:
            logger.error("[DB] Error when setting up SQL table: {}".format(e))
        return False

    async def get_last_updated(self, source_id):
        try:
            db = await self.db
            table = self._get_table()
            result = await db.execute(table.select().where(table.c.source_id==source_id).limit(1))
            item = await result.first()
            tstamp = item["updated"] if item else datetime.utcnow()
            return tstamp
        except Exception as e:
            logger.error("[DB] Error when querying for last updated item on {}".format(source_id))
            logger.exception(e)
        return None

    async def get_known_posts(self, source_id, post_ids):
        results = []
        try:
            db = await self.db
            table = self._get_table()
            sql = select(columns=[table.c.post_id]).where(table.c.source_id==source_id).where(table.c.post_id.in_(post_ids))
            db_res = await db.execute(sql)
            for r in await db_res.fetchall():
                results.append(r[0])
        except Exception as e:
            logger.error("[DB] Error when querying for posts {}".format(post_ids))
            logger.exception(e)
        return results

    async def get_post(self, target_id, post_id):
        try:
            db = await self.db
            table = self._get_table()
            sql = table.select().where(table.c.target_id==target_id).where(table.c.post_id==post_id).limit(1)
            result = await db.execute(sql)
            item = await result.first()
            if item:
                item = dict(item)
                item["target_doc"] = json.loads(item["target_doc"]) if item.get("target_doc") != "" else {}
            return item
        except Exception as e:
            logger.error("[DB] Error when querying for a post [{}] on {}".format(post_id, target_id))
            logger.error(e)
        return None

    async def insert_post(self, **kwargs):
        try:
            db = await self.db
            table = self._get_table()
            sql = table.insert().values(
                target_id=kwargs.get("target_id"), 
                post_id=kwargs.get("post_id"), 
                source_id=kwargs.get("source_id"), 
                text=kwargs.get("text"), 
                sticky=int(kwargs.get("sticky", "0")), 
                created=kwargs.get("created"),
                updated=kwargs.get("updated"),
                target_doc=json.dumps(kwargs.get("target_doc")) if kwargs.get("target_doc") else "" 
            ) 
            await db.execute(sql)
            logger.info("[DB] Post {} {} was saved!".format(kwargs["source_id"], kwargs["post_id"]))
            return True
        except Exception as e:
            logger.error("[DB] Error when saving {}".format(kwargs))
            logger.error(e)
        return False

    async def update_post(self, **kwargs):
        try:
            db = await self.db
            table = self._get_table()
            sql = table.update().where(
                    table.c.target_id==kwargs.get("target_id")
                ).where(
                    table.c.post_id==kwargs.get("post_id")
                ).values(
                    target_id=kwargs.get("target_id"), 
                    post_id=kwargs.get("post_id"), 
                    source_id=kwargs.get("source_id"), 
                    text=kwargs.get("text"), 
                    sticky=int(kwargs.get("sticky", "0")), 
                    created=kwargs.get("created"),
                    updated=kwargs.get("updated"),
                    target_doc=json.dumps(kwargs.get("target_doc")) if kwargs.get("target_doc") else "" 
                ) 
            await db.execute(sql)
            logger.info("[DB] Post {} {} was updated!".format(kwargs.get("post_id"), kwargs.get("target_id")))
            return True
        except Exception as e:
            logger.error("[DB] Error when updating for a post [{}] on {}".format(kwargs.get("post_id"), kwargs.get("target_id")))
            logger.error(e)
        return False

    async def delete_post(self, target_id, post_id):
        try:
            db = await self.db
            table = self._get_table()
            sql = table.delete().where(table.c.target_id==target_id).where(table.c.post_id==post_id)
            await db.execute(sql)
            logger.info("[DB] Post {} {} was deleted!".format(target_id, post_id))
            return True
        except Exception as e:
            logger.error("[DB] Error when deleting for a post [{}] on {}".format(post_id, target_id))
            logger.error(e)
        return False
