import logging
import sqlite3
import time
import sys
from logging.config import dictConfig

from flask import Flask, json, render_template, request, url_for, redirect, flash, abort


class UTCFormatter(logging.Formatter):
    converter = time.gmtime


class ErrorFilter(logging.Filter):
    def filter(self, record):
        allow = record.levelno < logging.ERROR

        return allow


db_connection_counter = 0


dictConfig({
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'default': {
            '()': UTCFormatter,
            'format': '%(asctime)s [%(levelname)s]:%(name)s: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%SZ'
        }
    },
    'filters': {
        'error_filter': {
            '()': ErrorFilter,
        }
    },
    'handlers': {
        'console_out': {
            'formatter': 'default',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',
            'filters': ['error_filter']
            # 'level': 'DEBUG',
        },
        'console_err': {
            'formatter': 'default',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stderr',
            'level': 'ERROR',
        },
        'wsgi': {
            'formatter': 'default',
            'class': 'logging.StreamHandler',
            'stream': 'ext://flask.logging.wsgi_errors_stream',
        },
    },
    'root': {  # root logger
        'handlers': ['console_out', 'console_err'],
        'level': 'DEBUG',
        'propagate': False
    },
})


# Function to get a database connection.
# This function connects to database with the name `database.db`
def get_db_connection():
    global db_connection_counter
    connection = sqlite3.connect('database.db')
    db_connection_counter += 1
    connection.row_factory = sqlite3.Row

    return connection


# Function to get a post using its ID
def get_post(post_id):
    connection = get_db_connection()
    post = connection.execute('SELECT * FROM posts WHERE id = ?',
                              (post_id,)).fetchone()
    connection.close()

    return post


# Function to get total count of posts
def get_post_count():
    connection = get_db_connection()
    post_count_row = connection.execute('SELECT COUNT(*) FROM posts').fetchone()
    connection.close()

    if post_count_row is not None:
        return post_count_row[0]

    return 0


def check_health():
    try:
        connection = get_db_connection()
        connection.execute('SELECT 1 FROM posts').fetchone()
    except Exception as e:
        print('Exception: {0}'.format(e))

        return False

    return True


def handle_flask_error(e):
    app_logger = logging.getLogger('app')
    app_logger.error('{0}'.format(e))

    return render_template('error.html'), e.code



# Define the Flask application
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your secret key'
app.register_error_handler(Exception, handle_flask_error)

app.logger.debug('Demonstrating app debug level log')
app.logger.info('Demonstrating app info level log')
app.logger.warning('Demonstrating app warning level log')
app.logger.error('Demonstrating app error level log')
app.logger.critical('Demonstrating app critical level log')


# Define the main route of the web application 
@app.route('/')
def index():
    connection = get_db_connection()
    posts = connection.execute('SELECT * FROM posts').fetchall()
    connection.close()
    return render_template('index.html', posts=posts)


# Define how each individual article is rendered 
# If the post ID is not found a 404 page is shown
@app.route('/<int:post_id>')
def post(post_id):
    post = get_post(post_id)
    if post is None:
        app.logger.error('Non-existing article accessed with id {0}. "404" page returned'.format(post_id))
        return render_template('404.html'), 404
    else:
        app.logger.info('Article {0} retrieved'.format(post["title"]))
        return render_template('post.html', post=post)


# Define the About Us page
@app.route('/about')
def about():
    app.logger.info('"About Us" page retrieved')
    return render_template('about.html')


# Define the post creation functionality 
@app.route('/create', methods=('GET', 'POST'))
def create():
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']

        if not title:
            flash('Title is required!')
            app.logger.error('Submitting article without title')
        else:
            connection = get_db_connection()
            connection.execute('INSERT INTO posts (title, content) VALUES (?, ?)', (title, content))
            connection.commit()
            app.logger.info('New article {0} created'.format(title))
            connection.close()
            return redirect(url_for('index'))

    return render_template('create.html')


# Define healthcheck endpoint
@app.route('/healthz')
def healthcheck():
    if check_health():
        response = app.response_class(
            response=json.dumps({"result": "OK - healthy"}),
            status=200,
            mimetype='application/json'
        )
    else:
        response = app.response_class(
            response=json.dumps({"result": "ERROR - unhealthy"}),
            status=500,
            mimetype='application/json'
        )

    return response


# Define metrics endpoint
@app.route('/metrics')
def metrics():
    post_count = get_post_count()
    response = app.response_class(
        response=json.dumps({
            "db_connection_count": db_connection_counter,
            "post_count": post_count,
        }),
        status=200,
        mimetype='application/json'
    )

    return response


# start the application on port 3111
if __name__ == "__main__":
    app.run(host='0.0.0.0', port='3111')
