from .. import db
from geopy.geocoders import Nominatim


class ZIPCode(db.Model):
    __tablename__ = 'zip_codes'
    id = db.Column(db.Integer, primary_key=True)
    zip_code = db.Column(db.String(5), unique=True, index=True)
    users = db.relationship('User', backref='zip_code', lazy='dynamic')
    addresses = db.relationship('Address', backref='zip_code', lazy='dynamic')
    longitude = db.Column(db.Float)
    latitude = db.Column(db.Float)

    def __init__(self, zip_code):
        """
        If possible, the helper methods get_by_zip_code and create_zip_code
        should be used instead of explicitly using this constructor.
        """
        getcoords = Nominatim(country_bias='us')
        loc = getcoords.geocode(zip_code)
        if loc is None:
            raise ValueError('zip code \'%s\' is invalid' % zip_code)
        self.longitude = loc.longitude
        self.latitude = loc.latitude
        self.zip_code = zip_code

    @staticmethod
    def get_by_zip_code(zip_code):
        """Helper for searching by 5 digit zip codes."""
        result = ZIPCode.query.filter_by(zip_code=zip_code).first()
        return result

    @staticmethod
    def create_zip_code(zip_code):
        """
        Helper to create a ZIPCode entry. Returns the newly created ZIPCode
        or the existing entry if zip_code is already in the table.
        """
        result = ZIPCode.get_by_zip_code(zip_code)
        if result is None:
            result = ZIPCode(zip_code)
            db.session.add(result)
            db.session.commit()
        return result

    @staticmethod
    def generate_fake():
        """
        Populate the zip_codes table with arbitrary but real zip codes.
        The zip codes generated by the faker library
        are not necessarily valid or guaranteed to be located in the US.
        """
        zip_codes = ['19104', '01810', '02420', '75205', '94305',
                     '47906', '60521']

        for zip_code in zip_codes:
            ZIPCode.create_zip_code(zip_code)

    def __repr__(self):
        return '<ZIPCode \'%s\'>' % self.zip_code


class Address(db.Model):
    __tablename__ = 'addresses'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)         # ABC MOVERS
    street_address = db.Column(db.Text)  # 1500 E MAIN AVE STE 201
    city = db.Column(db.Text)
    state = db.Column(db.String(2))
    zip_code_id = db.Column(db.Integer, db.ForeignKey('zip_codes.id'))
    resources = db.relationship('Resource', backref='address', lazy='dynamic')

    def __init__(self, name, street_address, city, state):
        """
        If possible, the helper methods get_by_address and create_address
        should be used instead of explicitly using this constructor.
        """
        self.name = name
        self.street_address = street_address
        self.city = city
        self.state = state

    @staticmethod
    def get_by_address(name, street_address, city, state):
        """Helper for searching by all address fields."""
        result = Address.query.filter_by(name=name,
                                         street_address=street_address,
                                         city=city,
                                         state=state).first()
        return result

    @staticmethod
    def create_address(name, street_address, city, state):
        """
        Helper to create an Address entry. Returns the newly created Address
        or the existing entry if all address fields are already in the table.
        """
        result = Address.get_by_address(name,
                                        street_address,
                                        city,
                                        state)
        if result is None:
            result = Address(name=name,
                             street_address=street_address,
                             city=city,
                             state=state)
            db.session.add(result)
            db.session.commit()
        return result

    @staticmethod
    def generate_fake():
        """Generate addresses that actually exist for map testing."""
        from faker import Faker

        fake = Faker()

        addresses = [
            {
                'street_address': '6511 Candlebrite Dr',
                'city': 'San Antonio',
                'state': 'TX',
                'zip_code': '78244'
            }, {
                'street_address': '6111 Lausche Ave',
                'city': 'Cleveland',
                'state': 'OH',
                'zip_code': '44103'
            }, {
                'street_address': '11600 Seminole Blvd',
                'city': 'Largo',
                'state': 'FL',
                'zip_code': '33778'
            }, {
                'street_address': '6400 Westown Pkwy',
                'city': 'W Des Moines',
                'state': 'IA',
                'zip_code': '50266'
            }, {
                'street_address': '3650 Spruce St',
                'city': 'Philadelphia',
                'state': 'PA',
                'zip_code': '19104'
            }, {
                'street_address': '3650 Spruce St',
                'city': 'Philadelphia',
                'state': 'PA',
                'zip_code': '19104'
            }, {
                'street_address': '6879 N Wildwood Ave',
                'city': 'Chicago',
                'state': 'IL',
                'zip_code': '60646'
            }, {
                'street_address': '3809 Maple Ave',
                'city': 'Castalia',
                'state': 'OH',
                'zip_code': '44824'
            }, {
                'street_address': '9001 Triple Ridge Rde',
                'city': 'Fairfax Sta',
                'state': 'VA',
                'zip_code': '22039'
            }, {
                'street_address': '610 Lake Forbing Dr',
                'city': 'Shreveport',
                'state': 'LA ',
                'zip_code': '71106'
            }
        ]
        for address in addresses:
            a = Address.create_address(
                name=fake.name(),
                street_address=address['street_address'],
                city=address['city'],
                state=address['state']
            )
            a.zip_code = ZIPCode.create_zip_code(address['zip_code'])
            db.session.add(a)
            db.session.commit()

    def __repr__(self):
        return '<Address \'%s\'>' % self.name
