"""Models for SQLAlchemy."""
import json
from datetime import datetime
import logging

from sqlalchemy import (
    Boolean, Column, DateTime, ForeignKey, Index, Integer, String, Text,
    distinct)
from sqlalchemy.ext.declarative import declarative_base

import homeassistant.util.dt as dt_util
from homeassistant.core import Event, EventOrigin, State, split_entity_id
from homeassistant.remote import JSONEncoder

# SQLAlchemy Schema
# pylint: disable=invalid-name
Base = declarative_base()

SCHEMA_VERSION = 3

_LOGGER = logging.getLogger(__name__)


class Events(Base):  # type: ignore
    """Event history data."""

    __tablename__ = 'events'
    event_id = Column(Integer, primary_key=True)
    event_type = Column(String(32), index=True)
    event_data = Column(Text)
    origin = Column(String(32))
    time_fired = Column(DateTime(timezone=True), index=True)
    created = Column(DateTime(timezone=True), default=datetime.utcnow)

    @staticmethod
    def from_event(event):
        """Create an event database object from a native event."""
        return Events(event_type=event.event_type,
                      event_data=json.dumps(event.data, cls=JSONEncoder),
                      origin=str(event.origin),
                      time_fired=event.time_fired)

    def to_native(self):
        """Convert to a natve HA Event."""
        try:
            return Event(
                self.event_type,
                json.loads(self.event_data),
                EventOrigin(self.origin),
                _process_timestamp(self.time_fired)
            )
        except ValueError:
            # When json.loads fails
            _LOGGER.exception("Error converting to event: %s", self)
            return None


class States(Base):   # type: ignore
    """State change history."""

    __tablename__ = 'states'
    state_id = Column(Integer, primary_key=True)
    domain = Column(String(64))
    entity_id = Column(String(255))
    state = Column(String(255))
    attributes = Column(Text)
    event_id = Column(Integer, ForeignKey('events.event_id'))
    last_changed = Column(DateTime(timezone=True), default=datetime.utcnow)
    last_updated = Column(DateTime(timezone=True), default=datetime.utcnow,
                          index=True)
    created = Column(DateTime(timezone=True), default=datetime.utcnow)

    __table_args__ = (Index('states__state_changes',
                            'last_changed', 'last_updated', 'entity_id'),
                      Index('states__significant_changes',
                            'domain', 'last_updated', 'entity_id'),
                      Index('ix_states_entity_id_created',
                            'entity_id', 'created'),
                      Index('ix_states_created_domain',
                            'created', 'domain'),)

    @staticmethod
    def from_event(event):
        """Create object from a state_changed event."""
        entity_id = event.data['entity_id']
        state = event.data.get('new_state')

        dbstate = States(entity_id=entity_id)

        # State got deleted
        if state is None:
            dbstate.state = ''
            dbstate.domain = split_entity_id(entity_id)[0]
            dbstate.attributes = '{}'
            dbstate.last_changed = event.time_fired
            dbstate.last_updated = event.time_fired
        else:
            dbstate.domain = state.domain
            dbstate.state = state.state
            dbstate.attributes = json.dumps(dict(state.attributes),
                                            cls=JSONEncoder)
            dbstate.last_changed = state.last_changed
            dbstate.last_updated = state.last_updated

        return dbstate

    def to_native(self):
        """Convert to an HA state object."""
        try:
            return State(
                self.entity_id, self.state,
                json.loads(self.attributes),
                _process_timestamp(self.last_changed),
                _process_timestamp(self.last_updated)
            )
        except ValueError:
            # When json.loads fails
            _LOGGER.exception("Error converting row to state: %s", self)
            return None


class RecorderRuns(Base):   # type: ignore
    """Representation of recorder run."""

    __tablename__ = 'recorder_runs'
    run_id = Column(Integer, primary_key=True)
    start = Column(DateTime(timezone=True), default=datetime.utcnow)
    end = Column(DateTime(timezone=True))
    closed_incorrect = Column(Boolean, default=False)
    created = Column(DateTime(timezone=True), default=datetime.utcnow)

    __table_args__ = (Index('ix_recorder_runs_start_end', 'start', 'end'),)

    def entity_ids(self, point_in_time=None):
        """Return the entity ids that existed in this run.

        Specify point_in_time if you want to know which existed at that point
        in time inside the run.
        """
        from sqlalchemy.orm.session import Session

        session = Session.object_session(self)

        assert session is not None, 'RecorderRuns need to be persisted'

        query = session.query(distinct(States.entity_id)).filter(
            States.last_updated >= self.start)

        if point_in_time is not None:
            query = query.filter(States.last_updated < point_in_time)
        elif self.end is not None:
            query = query.filter(States.last_updated < self.end)

        return [row[0] for row in query]

    def to_native(self):
        """Return self, native format is this model."""
        return self


class SchemaChanges(Base):   # type: ignore
    """Representation of schema version changes."""

    __tablename__ = 'schema_changes'
    change_id = Column(Integer, primary_key=True)
    schema_version = Column(Integer)
    changed = Column(DateTime(timezone=True), default=datetime.utcnow)


def _process_timestamp(ts):
    """Process a timestamp into datetime object."""
    if ts is None:
        return None
    elif ts.tzinfo is None:
        return dt_util.UTC.localize(ts)

    return dt_util.as_utc(ts)