from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple, Union
from uuid import uuid4

from flask import current_app
from strenum import StrEnum

from alerta.app import db
from alerta.database.base import Query
from alerta.utils.format import DateTime
from alerta.utils.response import absolute_url

JSON = Dict[str, Any]


class BlackoutStatus(StrEnum):

    Pending = 'pending'
    Active = 'active'
    Expired = 'expired'


class Blackout:

    def __init__(self, environment: str, **kwargs) -> None:
        if not environment:
            raise ValueError('Missing mandatory value for "environment"')
        if kwargs.get('resource') == '':
            raise ValueError('resource can not be an empty string')
        if kwargs.get('event') == '':
            raise ValueError('event can not be an empty string')
        if kwargs.get('group') == '':
            raise ValueError('group can not be an empty string')
        if kwargs.get('origin') == '':
            raise ValueError('origin can not be an empty string')

        start_time = kwargs.get('start_time', None) or datetime.utcnow()
        if kwargs.get('end_time', None):
            end_time = kwargs['end_time']
            duration = int((end_time - start_time).total_seconds())
        else:
            duration = kwargs.get('duration', None) or current_app.config['BLACKOUT_DURATION']
            end_time = start_time + timedelta(seconds=duration)

        self.id = kwargs.get('id') or str(uuid4())
        self.environment = environment
        self.service = kwargs.get('service', None) or list()
        self.resource = kwargs.get('resource', None)
        self.event = kwargs.get('event', None)
        self.group = kwargs.get('group', None)
        self.tags = kwargs.get('tags', None) or list()
        self.origin = kwargs.get('origin', None)
        self.customer = kwargs.get('customer', None)
        self.start_time = start_time
        self.end_time = end_time
        self.duration = duration
        self.remaining = kwargs.get('remaining', duration)

        self.user = kwargs.get('user', None)
        self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.utcnow()
        self.text = kwargs.get('text', None)

        if self.environment:
            self.priority = 0
        if self.origin:
            self.priority = 1
        if self.resource and not self.event:
            self.priority = 2
        elif self.service:
            self.priority = 3
        elif self.event and not self.resource:
            self.priority = 4
        elif self.group:
            self.priority = 5
        elif self.resource and self.event:
            self.priority = 6
        elif self.tags:
            self.priority = 7
        if self.origin:
            self.priority = 8

    @property
    def status(self):
        now = datetime.utcnow()
        if self.start_time <= now < self.end_time:
            return BlackoutStatus.Active
        if self.start_time > now:
            return BlackoutStatus.Pending
        if self.end_time <= now:
            return BlackoutStatus.Expired

    @classmethod
    def parse(cls, json: JSON) -> 'Blackout':
        if not isinstance(json.get('service', []), list):
            raise ValueError('service must be a list')
        if not isinstance(json.get('tags', []), list):
            raise ValueError('tags must be a list')

        return Blackout(
            id=json.get('id', None),
            environment=json['environment'],
            service=json.get('service', list()),
            resource=json.get('resource', None),
            event=json.get('event', None),
            group=json.get('group', None),
            tags=json.get('tags', list()),
            origin=json.get('origin', None),
            customer=json.get('customer', None),
            start_time=DateTime.parse(json['startTime']) if 'startTime' in json else None,
            end_time=DateTime.parse(json['endTime']) if 'endTime' in json else None,
            duration=json.get('duration', None),
            user=json.get('user', None),
            text=json.get('text', None)
        )

    @property
    def serialize(self) -> Dict[str, Any]:
        return {
            'id': self.id,
            'href': absolute_url('/blackout/' + self.id),
            'priority': self.priority,
            'environment': self.environment,
            'service': self.service,
            'resource': self.resource,
            'event': self.event,
            'group': self.group,
            'tags': self.tags,
            'origin': self.origin,
            'customer': self.customer,
            'startTime': self.start_time,
            'endTime': self.end_time,
            'duration': self.duration,
            'status': self.status,
            'remaining': self.remaining,
            'user': self.user,
            'createTime': self.create_time,
            'text': self.text
        }

    def __repr__(self) -> str:
        more = ''
        if self.service:
            more += f'service={self.service!r}, '
        if self.resource:
            more += f'resource={self.resource!r}, '
        if self.event:
            more += f'event={self.event!r}, '
        if self.group:
            more += f'group={self.group!r}, '
        if self.tags:
            more += f'tags={self.tags!r}, '
        if self.origin:
            more += f'origin={self.origin!r}, '
        if self.customer:
            more += f'customer={self.customer!r}, '

        return 'Blackout(id={!r}, priority={!r}, status={!r}, environment={!r}, {}start_time={!r}, end_time={!r}, remaining={!r})'.format(
            self.id,
            self.priority,
            self.status,
            self.environment,
            more,
            self.start_time,
            self.end_time,
            self.remaining
        )

    @classmethod
    def from_document(cls, doc: Dict[str, Any]) -> 'Blackout':
        return Blackout(
            id=doc.get('id', None) or doc.get('_id'),
            priority=doc.get('priority', None),
            environment=doc['environment'],
            service=doc.get('service', list()),
            resource=doc.get('resource', None),
            event=doc.get('event', None),
            group=doc.get('group', None),
            tags=doc.get('tags', list()),
            origin=doc.get('origin', None),
            customer=doc.get('customer', None),
            start_time=doc.get('startTime', None),
            end_time=doc.get('endTime', None),
            duration=doc.get('duration', None),
            remaining=doc.get('remaining', None),
            user=doc.get('user', None),
            create_time=doc.get('createTime', None),
            text=doc.get('text', None)
        )

    @classmethod
    def from_record(cls, rec) -> 'Blackout':
        return Blackout(
            id=rec.id,
            priority=rec.priority,
            environment=rec.environment,
            service=rec.service,
            resource=rec.resource if rec.resource != '' else None,
            event=rec.event if rec.event != '' else None,
            group=rec.group if rec.group != '' else None,
            tags=rec.tags,
            origin=rec.origin if rec.origin != '' else None,
            customer=rec.customer,
            start_time=rec.start_time,
            end_time=rec.end_time,
            duration=rec.duration,
            remaining=rec.remaining,
            user=rec.user,
            create_time=rec.create_time,
            text=rec.text
        )

    @classmethod
    def from_db(cls, r: Union[Dict, Tuple]) -> 'Blackout':
        if isinstance(r, dict):
            return cls.from_document(r)
        elif isinstance(r, tuple):
            return cls.from_record(r)

    # create a blackout
    def create(self) -> 'Blackout':
        return Blackout.from_db(db.create_blackout(self))

    # get a blackout
    @staticmethod
    def find_by_id(id: str, customers: List[str] = None) -> Optional['Blackout']:
        return Blackout.from_db(db.get_blackout(id, customers))

    @staticmethod
    def find_all(query: Query = None, page: int = 1, page_size: int = 1000) -> List['Blackout']:
        return [Blackout.from_db(blackout) for blackout in db.get_blackouts(query, page, page_size)]

    @staticmethod
    def count(query: Query = None) -> int:
        return db.get_blackouts_count(query)

    def update(self, **kwargs) -> 'Blackout':
        if kwargs.get('startTime'):
            kwargs['startTime'] = DateTime.parse(kwargs['startTime'])
        if kwargs.get('endTime'):
            kwargs['endTime'] = DateTime.parse(kwargs['endTime'])
        return Blackout.from_db(db.update_blackout(self.id, **kwargs))

    def delete(self) -> bool:
        return db.delete_blackout(self.id)
