from distribution import app
from distribution.validator import (coerce_empty_to_none, coerce_empty_to_zero,
                                    coerce_string_to_utc_string)

TAXONOMY_TOPIC_TYPES = app.config['TAXONOMY_TOPIC_TYPES']
MODELS_MAP = app.config['MODELS_MAP']

# Common and reusable Cerberus rules
CERBERUS_RULE_REQUIRED_NONEMPTY_STRING = {
    'type': 'string',
    'required': True,
    'empty': False
}

CERBERUS_RULE_OPTIONAL_STRING = {
    'type': 'string',
    'nullable': True,
    'default': None
}

CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING = CERBERUS_RULE_OPTIONAL_STRING
CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING['coerce'] = coerce_empty_to_none

CERBERUS_RULE_REQUIRED_UUID = {
    'type': 'uuid',
    'required': True,
    'empty': False
}

CERBERUS_RULE_REQUIRED_DATETIME_STRING = {
    'type': 'required_datetime_string',
    'required': True,
    'coerce': coerce_string_to_utc_string
}

CERBERUS_RULE_OPTIONAL_DATETIME_STRING = {
    'type': 'optional_datetime_string',
    'nullable': True,
    'coerce': coerce_string_to_utc_string,
    'default': None
}

CERBERUS_RULE_IMAGE_RENDITIONS = {
    'width': {
        'type': 'string',
        'nullable': True,
        'default': None,
        'coerce': str
    },
    'uri': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
    'density': CERBERUS_RULE_OPTIONAL_STRING
}

CERBERUS_RULE_IMAGE_EXTRA_FIELDS = {
    'image': {
        'type': 'dict',
        'schema': {
            'aspectRatio': {
                'type': 'float',
                'required': False,
            },
            'height': {
                'type': 'integer',
                'required': False,
            },
            'width': {
                'type': 'integer',
                'required': False,
            },
            'credit': CERBERUS_RULE_OPTIONAL_STRING,
        }
    }
}

CERBERUS_RULE_TAXONOMY_TOPIC_LIST = {
    'type': 'list',
    'default': [],
    'schema': CERBERUS_RULE_REQUIRED_UUID
}

CERBERUS_RULE_TAXONOMY_UNKNOWNTERM_LIST = {
    'type': 'list',
    'default': [],
    'schema': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
}

CERBERUS_RULE_SCHEDULE_SCHEMA = {
    'type': 'dict',
    'default': {},
    'required': False,
    'schema': {
        'episode': {
            'type': 'dict',
            'nullable': True,
            'default': None,
            'schema': {
                'start_date': CERBERUS_RULE_REQUIRED_DATETIME_STRING,
                'end_date': CERBERUS_RULE_REQUIRED_DATETIME_STRING,
                'series_id': {'type': 'string', 'nullable': False, 'coerce': str},
                'season_id': {'type': 'string', 'nullable': False, 'coerce': str},
                'episode_id': {'type': 'string', 'nullable': False, 'coerce': str},
                'house_number': CERBERUS_RULE_OPTIONAL_STRING,
                'box_number': CERBERUS_RULE_OPTIONAL_STRING,
            }
        }
    }
}

CERBERUS_RULE_PHOTO_SCHEMA = {
    'type': 'dict',
    'default': {},
    'required': False,
    'schema': {
        'exif': {
            'type': 'dict',
            'nullable': True,
            'default': None,
            'schema': {
                'aperture': CERBERUS_RULE_OPTIONAL_STRING,
                'focal_length': CERBERUS_RULE_OPTIONAL_STRING,
                'focal-length': {'rename': 'focal_length'},
                'camera': CERBERUS_RULE_OPTIONAL_STRING,
                'iso': {'type': 'integer', 'coerce': coerce_empty_to_zero},
                'shutter_speed': CERBERUS_RULE_OPTIONAL_STRING,
                'shutter-speed': {'rename': 'shutter_speed'},
            }
        },
        'location': {
            'type': 'dict',
            'nullable': True,
            'default': None,
            'schema': {
                'latitude': {'type': 'float', 'nullable': True, 'required': True},
                'longitude': {'type': 'float', 'nullable': True, 'required': True},
                'name': CERBERUS_RULE_OPTIONAL_STRING,
            }
        },
        'sizes': {
            'type': 'dict',
            'nullable': True,
            'default': None,
            'schema': {
                'medium_500': CERBERUS_RULE_OPTIONAL_STRING,
                'medium-500': {'rename': 'medium_500'},
                'medium_800': CERBERUS_RULE_OPTIONAL_STRING,
                'medium-800': {'rename': 'medium_800'},
                'small_320': CERBERUS_RULE_OPTIONAL_STRING,
                'small-320': {'rename': 'small_320'},
                'square_244': CERBERUS_RULE_OPTIONAL_STRING,
                'square-244': {'rename': 'square_244'},
                'large_1024': CERBERUS_RULE_OPTIONAL_STRING,
                'large-1024': {'rename': 'large_1024'},
                'square_75': CERBERUS_RULE_OPTIONAL_STRING,
                'square-75': {'rename': 'square_75'},
                'large_1600': CERBERUS_RULE_OPTIONAL_STRING,
                'large-1600': {'rename': 'large_1600'},
                'large_2048': CERBERUS_RULE_OPTIONAL_STRING,
                'large-2048': {'rename': 'large_2048'},
                'medium_640': CERBERUS_RULE_OPTIONAL_STRING,
                'medium-640': {'rename': 'medium_640'},
                'small_240': CERBERUS_RULE_OPTIONAL_STRING,
                'small-240': {'rename': 'small_240'},
                'thumbnail_100': CERBERUS_RULE_OPTIONAL_STRING,
                'thumbnail-100': {'rename': 'thumbnail_100'},
                'original': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
            }
        }
    }
}

CERBERUS_RULE_SERIES_SCHEMA = {
    'type': 'dict',
    'default': {},
    'required': False,
    'schema': {
        'type': CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING,
        'episodes': {
            'type': 'list',
            'nullable': True,
            'default': None,
            'schema': {
                'type': 'dict',
                'schema': {
                    'id': {'type': 'integer', 'nullable': False, 'coerce': int},
                    'type': CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING,
                    'production_kind': CERBERUS_RULE_OPTIONAL_STRING,
                    'title': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'language': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'genre': CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING,
                    'sub_genre': CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING,
                    'audience_description': CERBERUS_RULE_OPTIONAL_NOTEMPTY_STRING,
                    'synopsis': CERBERUS_RULE_OPTIONAL_STRING,
                    'number': {'type': 'integer', 'nullable': False, 'coerce': int},
                    'season_number': {'type': 'string', 'nullable': False, 'coerce': str},
                    'production_year': {'type': 'integer', 'nullable': True, 'coerce': int},
                    'pg_rating': CERBERUS_RULE_OPTIONAL_STRING,
                    'vchip_rating': CERBERUS_RULE_OPTIONAL_STRING,
                    'global_id': CERBERUS_RULE_OPTIONAL_STRING,
                    'media': {
                        'type': 'list',
                        'nullable': True,
                        'default': None,
                        'schema': {
                            'type': 'dict',
                            'schema': {
                                'title': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                                'house_number': CERBERUS_RULE_OPTIONAL_STRING,
                                'box_number': CERBERUS_RULE_OPTIONAL_STRING,
                                'segment': {'type': 'integer', 'nullable': True, 'coerce': int},
                                'tcin': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                                'tcout': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                                'duration': CERBERUS_RULE_OPTIONAL_STRING,
                            }
                        }
                    },
                }
            }
        }
    }
}

CERBERUS_RULE_ARTICLE_SCHEMA = {
    'type': 'dict',
    'nullable': True,
    'default': None,
    'schema': {
        'kickers': {
            'type': 'list',
            'nullable': True,
            'default': None,
            'schema': {
                'name': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                'url': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
            }
        }
    }
}


# Cerberus schema to validate incoming requests
BASIC_SCHEMA = {
    'sequence': {
        'type': 'string',
        'required': True,
        'allowed': ['chunk', 'current', 'total', 'unsorted', 'upsert']
    },
    'domain': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
    'model': {
        'type': 'string',
        'required': False,
        'allowed': MODELS_MAP.keys(),
        'default': 'feeditem'
    },
    'items': {
        'type': 'list',
        'required': True,
        'empty': False
    }
}

VALIDATION_SCHEMA = {
    'sequence': {
        'type': 'string',
        'required': True,
        'allowed': ['validate']
    },
    'domain': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
    'model': {
        'type': 'string',
        'required': False,
        'allowed': MODELS_MAP.keys(),
        'default': 'feeditem'
    },
    'items': {
        'type': 'list',
        'required': True,
        'empty': False
    }
}

# Cerberus schema to validate incoming delete requests
DELETE_SCHEMA = {
    'sequence': {'type': 'string', 'required': True, 'allowed': ['delete']},
    'domain': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
    'items': {
        'type': 'list',
        'required': True,
        'empty': False,
        'schema': {
            'type': 'string',
            'required': True,
            'empty': False
        }
    },
    'model': {
        'type': 'string',
        'required': False,
        'allowed': MODELS_MAP.keys(),
        'default': 'feeditem'
    },
}

# Cerberus schema to validate incoming item to post
POST_ITEM_SCHEMA = {
    'domain': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
    'item': {'type': 'dict', 'required': True, 'empty': False},
    'model': {
        'type': 'string',
        'required': False,
        'allowed': MODELS_MAP.keys(),
        'default': 'feeditem'
    },
}


class AssetSchema(dict):
    """
    Cerberus schema to validate assets
    Schema that represents metadata and required attributes that all the objects should have
    """
    def __init__(self):
        super(AssetSchema, self).__init__()
        self['content_type'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING.copy()
        self['id'] = {'type': 'string', 'coerce': str, 'required': True, 'empty': False}
        self['publication_datetime'] = CERBERUS_RULE_REQUIRED_DATETIME_STRING
        self['last_modified_datetime'] = CERBERUS_RULE_REQUIRED_DATETIME_STRING
        self['language'] = {
            'type': 'string',
            'required': True,
            'empty': False,
            'default': 'en-US',
        }
        self['parent'] = {
            'type': 'string',
            'nullable': True,
            'required': False,
            'empty': True,
        }


class SourceSystemSchema(dict):
    """Cerberus schema to validate source_system."""
    SOURCE_SYSTEM_IDS = app.config.get('SOURCE_SYSTEM_IDS')

    def __init__(self):
        super(SourceSystemSchema, self).__init__()
        self['id'] = {
            'type': 'string',
            'required': True,
            'empty': False
        }
        self['asset_id'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['account'] = CERBERUS_RULE_OPTIONAL_STRING
        self['asset_external_field_name'] = CERBERUS_RULE_OPTIONAL_STRING


class ImageSchema(dict):
    """Cerberus schema to validate images."""
    def __init__(self):
        super(ImageSchema, self).__init__()
        self['uri'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['title'] = CERBERUS_RULE_OPTIONAL_STRING
        self['order'] = {
            'type': 'integer',
            'required': False,
            'default': 0
        }
        self['source_system'] = {
            'type': 'dict',
            'required': True,
            'schema': SourceSystemSchema()
        }
        """
        self['rights_system'] = {
            'type': 'dict',
            'required': False,
            'nulleable': True,
            'empty': True,
            'schema': SourceSystemSchema()
        }
        """
        self['alt_text'] = CERBERUS_RULE_OPTIONAL_STRING
        self['credit'] = CERBERUS_RULE_OPTIONAL_STRING
        self['caption'] = CERBERUS_RULE_OPTIONAL_STRING
        self['asset_provider'] = {
            'type': 'dict',
            'required': False,
            'nullable': True,
            'schema': {
                'uri': CERBERUS_RULE_OPTIONAL_STRING,
                'asset_id': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                'id': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
            }
        }
        self['aspect_ratio'] = {
            'type': 'float',
            'required': False
        }
        self['height'] = {
            'type': 'integer',
            'required': False
        }
        self['width'] = {
            'type': 'integer',
            'required': False
        }

        self['croppings'] = {
            'type': 'list',
            'required': False,
            'default': [],
            'schema': {
                'type': 'dict',
                'schema': {
                    'aspect_ratio_key': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'uri': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'instructions': {
                        'type': 'dict',
                        'required': True,
                        'schema': {
                            'x_coordinate': {
                                'type': 'integer',
                                'required': True,
                                'coerce': int
                            },
                            'y_coordinate': {
                                'type': 'integer',
                                'required': True,
                                'coerce': int
                            },
                            'height': {
                                'type': 'integer',
                                'required': True,
                                'coerce': int
                            },
                            'width': {
                                'type': 'integer',
                                'required': True,
                                'coerce': int
                            }
                        }
                    },
                    'renditions': {
                        'type': 'list',
                        'required': False,
                        'default': [],
                        'schema': {
                            'type': 'dict',
                            'schema': CERBERUS_RULE_IMAGE_RENDITIONS
                        }
                    }
                }
            }
        }

        self['renditions'] = {
            'type': 'list',
            'required': False,
            'default': [],
            'schema': {
                'type': 'dict',
                'schema': CERBERUS_RULE_IMAGE_RENDITIONS
            }
        }


class Schema(dict):
    def build_keys(self, keys, rule):
        for key in keys:
            self[key] = rule

    def build_booleans(self, keys, **kwargs):
        rule = {
          'type': 'boolean'
        }
        if kwargs:
            rule.update(kwargs)
        self.build_keys(keys, rule)

    def build_optional_strings(self, keys):
        self.build_keys(keys, CERBERUS_RULE_OPTIONAL_STRING)

    def build_required_strings(self, keys):
        self.build_keys(keys, CERBERUS_RULE_REQUIRED_NONEMPTY_STRING)

    def build_optional_int(self, keys):
        self.build_keys(keys, {'type': 'integer', 'nullable': True, 'default': None})

    def build_required_int(self, keys):
        self.build_keys(keys, {'type': 'integer', 'nullable': False, 'required': True})

    def build_optional_float(self, keys):
        self.build_keys(keys, {'type': 'float', 'nullable': True, 'default': None})

    def build_required_float(self, keys):
        self.build_keys(keys, {'type': 'float', 'nullable': False, 'required': True})


class VideoSchema(Schema):
    def __init__(self, *args, **kwargs):
        super(VideoSchema, self).__init__(*args, **kwargs)

        self.build_optional_strings(
            ['description', 'mime_type', 'thumbnail', 'language', 'format', 'expression', 'type']
        )

        self.build_required_strings(
            ['title', 'uri'])

        self.build_optional_float(
            ['duration', 'bitrate', 'frame_rate'])

        self.build_optional_int(
            ['audio_channels', 'audio_sample_rate', 'file_size', 'height', 'width'])

        self['source_system'] = {
            'type': 'dict',
            'required': True,
            'schema': SourceSystemSchema()
        }
        self.build_booleans(['default'], default=False)


class AnimalFactsSchemaBase(Schema):
    def __init__(self, *args, **kwargs):
        super(AnimalFactsSchemaBase, self).__init__(*args, **kwargs)
        self.build_optional_float([
            'captivity_lifespan_low', 'captivity_lifespan_median', 'captivity_lifespan_high',
            'wild_lifespan_low', 'wild_lifespan_median', 'wild_lifespan_high'
        ])
        self.build_string_list(['scientific_name'])
        self.build_taxonomy_ref(['animal_type'])

    def build_taxonomy_ref(self, fields):
        for field in fields:
            self[field] = {
                'type': 'dict',
                'required': False,
                'schema': {
                    'url': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'name': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
                }
            }

    def build_string_list(self, fields):
        for field in fields:
            self[field] = {
                'type': 'list',
                'default': [],
                'required': False,
                'schema': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
            }

    def build_multilingual_list(self, fields):
        for field in fields:
            self['field'] = {
                'type': 'list',
                'required': False,
                'schema': {
                    'type': 'dict',
                    'schema': {
                        'language': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                        'text': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
                    }
                }
            }


class AnimalFactsReferenceSchema(AnimalFactsSchemaBase):
    def __init__(self, *args, **kwargs):
        super(AnimalFactsReferenceSchema, self).__init__(*args, **kwargs)
        self['animal_subject'] = CERBERUS_RULE_REQUIRED_UUID
        self['name'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self.build_optional_strings([
            'lifespan_notes', 'diet_details', 'size', 'weight', 'population_trend',
            'size_comparison_label'
        ])
        self['relative_size_image'] = {
            'type': 'dict',
            'required': False,
            'schema': ImageSchema()
        }
        self['animal_reference_image'] = {
            'type': 'dict',
            'required': False,
            'schema': ImageSchema()
        }


class AnimalFactsTopicSchema(AnimalFactsSchemaBase):
    def __init__(self, *args, **kwargs):
        super(AnimalFactsTopicSchema, self).__init__(*args, **kwargs)
        self.build_optional_strings([
            'animal_class', 'conservation_status', 'kingdom', 'order', 'phylum', 'uriiucn'
        ])
        self.build_taxonomy_ref(['feeding_habit'])
        self.build_multilingual_list([
            'collective_noun', 'diet_details', 'lifespan_notes', 'habitat', 'predators', 'range',
            'size', 'weight'
        ])

        self.build_string_list([
            'scientific_name', 'family', 'genus',
            'species', 'subspecies'
        ])


class ImageGallerySchema(Schema):
    def __init__(self, *args, **kwargs):
        super(ImageGallerySchema, self).__init__(*args, **kwargs)
        self['title'] = CERBERUS_RULE_OPTIONAL_STRING
        self['aspect_ratio_type'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['gallery_size'] = {
            'type': 'string',
            'allowed': ['XS', 'S', 'M', 'L', 'FULL']
        }
        self.build_booleans([
            'show_image_asset_sources',
            'show_image_titles',
            'show_image_captions',
            'show_image_credits'
        ])

        self['images'] = {
            'type': 'list',
            'required': True,
            'schema': ImageSchema()
        }


class PullQuoteSchema(dict):
    def __init__(self, *args, **kwargs):
        super(PullQuoteSchema, self).__init__(*args, **kwargs)
        self['quote'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['name'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['quote_details'] = CERBERUS_RULE_OPTIONAL_STRING
        self['person_taxonomy_tag'] = {
            'type': 'dict',
            'required': False,
            'nullable': True,
            'anyof': [{'maxlength': 0}, {'schema': {
                'name': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                'uuid': CERBERUS_RULE_REQUIRED_UUID,
                'order': {
                    'type': 'integer',
                    'required': False
                }
            }}]
        }


class YouTubeSchema(Schema):
    def __init__(self, *args, **kwargs):
        super(YouTubeSchema, self).__init__(*args, **kwargs)
        self.build_optional_strings(
            ['title', 'description', 'credit']
        )
        self['embed_url'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['aspect_ratio'] = {
            'type': 'string',
            'required': True,
            'allowed': [
                '2:1',
                '3:4',
                '3:2',
                '4:3',
                '9:16',
                '16:9'
            ]
        }
        self['size'] = {
            'type': 'string',
            'required': True,
            'allowed': ['XS', 'S', 'M', 'L', 'FULL']
        }


class AudioSchema(Schema):
    def __init__(self, *args, **kwargs):
        super(AudioSchema, self).__init__(*args, **kwargs)
        self.build_optional_strings(
            ['title', 'alt_text', 'heading', 'caption', 'credit', 'asset_source', 'thumbnail']
        )
        self['uri'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['duration'] = {'type': 'integer', 'nullable': True, 'default': None}
        self['mime_type'] = {
            'type': 'string',
            'required': False,
            'allowed': [
                'audio/mp3',
                'audio/x-wav',
                'audio/mpeg'
            ]
        }
        self['source_system'] = {
            'type': 'dict',
            'required': True,
            'schema': SourceSystemSchema()
        }
        self.build_booleans(['show_title', 'show_caption', 'show_credit', 'show_thumbnail'])


class InteractiveSchema(Schema):
    def __init__(self, *args, **kwargs):
        super(InteractiveSchema, self).__init__(*args, **kwargs)

        self['fallback_image'] = {
            'type': 'dict',
            'required': True,
            'schema': ImageSchema()
        }
        self['fallback_text'] = CERBERUS_RULE_OPTIONAL_STRING


class BaseSchema(AssetSchema):
    # Cerberus schema to validate Items

    def __init__(self):
        super(BaseSchema, self).__init__()
        # ********* Basic info *********
        self['title'] = CERBERUS_RULE_REQUIRED_NONEMPTY_STRING
        self['abstract'] = CERBERUS_RULE_OPTIONAL_STRING
        # ********* Taxonomy fields *********
        self['keywords'] = {
            'type': 'list',
            'default': [],
            'schema': {'type': 'string'}
        }
        self['unknown_terms'] = {
            'type': 'dict',
            'default': {},
            'schema': {
                '': CERBERUS_RULE_TAXONOMY_UNKNOWNTERM_LIST
            }
        }
        for topic_type in TAXONOMY_TOPIC_TYPES:
            self[topic_type] = CERBERUS_RULE_TAXONOMY_TOPIC_LIST
            self['unknown_terms']['schema'][topic_type] = CERBERUS_RULE_TAXONOMY_UNKNOWNTERM_LIST

        self['uri'] = CERBERUS_RULE_OPTIONAL_STRING
        # ********* Content *********
        self['contributors'] = {
            'type': 'list',
            'default': [],
            'schema': {
                'type': 'dict',
                'schema': {
                    'role': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'name': CERBERUS_RULE_REQUIRED_NONEMPTY_STRING,
                    'order': {'type': 'integer', 'default': 0}
                }
            }
        }
