import os
from decimal import Decimal, InvalidOperation

from flask import render_template, redirect, url_for, flash, request, abort, send_file, current_app
from sqlalchemy import select

from app.blueprints.settings import settings_bp
from app.blueprints.settings.forms import AccountForm, CategoryForm, IncomeForm
import app.services.category_service as category_service
from app.models.category import Category
from app.extensions import db
from app.models.account import Account
from app.models.settings import Settings
from app.models.transaction import Transaction


@settings_bp.route('/')
def index():
    settings = Settings.query.first()
    return render_template('settings/index.html', active_page='settings',
                           settings=settings)


@settings_bp.route('/income', methods=['GET', 'POST'])
def income():
    settings = Settings.query.first()
    form = IncomeForm()

    if request.method == 'GET':
        if settings and settings.monthly_income is not None:
            form.monthly_income.data = str(settings.monthly_income)
        return render_template('settings/income.html', form=form,
                               active_page='settings')

    if form.validate_on_submit():
        raw = (form.monthly_income.data or '').strip()
        try:
            amount = Decimal(raw)
        except InvalidOperation:
            form.monthly_income.errors.append('Enter a valid numeric amount.')
            return render_template('settings/income.html', form=form,
                                   active_page='settings')

        if amount < 0:
            form.monthly_income.errors.append('Income must be zero or greater.')
            return render_template('settings/income.html', form=form,
                                   active_page='settings')

        if settings is None:
            settings = Settings(id=1, monthly_income=amount)
            db.session.add(settings)
        else:
            settings.monthly_income = amount
        db.session.commit()
        flash('Monthly income updated.', 'success')
        return redirect(url_for('settings.income'))

    return render_template('settings/income.html', form=form,
                           active_page='settings')


@settings_bp.route('/accounts', methods=['GET', 'POST'])
def accounts():
    form = AccountForm()
    if form.validate_on_submit():
        acct = Account(
            name=form.name.data.strip(),
            type=form.type.data,
            institution_name=(form.institution_name.data or '').strip() or None,
            is_active=True,
        )
        db.session.add(acct)
        db.session.commit()
        flash('Account added.', 'success')
        return redirect(url_for('settings.accounts'))

    all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
    return render_template(
        'settings/accounts.html',
        accounts=all_accounts,
        form=form,
        active_page='settings',
    )


@settings_bp.route('/accounts/<int:account_id>/edit', methods=['GET', 'POST'])
def account_edit(account_id):
    acct = Account.query.get_or_404(account_id)
    form = AccountForm(obj=acct)
    if form.validate_on_submit():
        acct.name = form.name.data.strip()
        acct.type = form.type.data
        acct.institution_name = (form.institution_name.data or '').strip() or None
        db.session.commit()
        flash('Account updated.', 'success')
        return redirect(url_for('settings.accounts'))

    return render_template(
        'settings/account_edit.html',
        form=form,
        account=acct,
        active_page='settings',
    )


@settings_bp.route('/accounts/<int:account_id>/delete', methods=['POST'])
def account_delete(account_id):
    acct = Account.query.get_or_404(account_id)
    has_txns = db.session.execute(
        select(Transaction.id).where(Transaction.account_id == acct.id).limit(1)
    ).first() is not None
    if has_txns:
        flash('Account in use — cannot be deleted.', 'error')
        return redirect(url_for('settings.accounts'))
    db.session.delete(acct)
    db.session.commit()
    flash('Account deleted.', 'success')
    return redirect(url_for('settings.accounts'))


# ── Categories ────────────────────────────────────────────────────────────────

@settings_bp.route('/categories', methods=['GET', 'POST'])
def categories():
    form = CategoryForm()
    if form.validate_on_submit():
        try:
            category_service.create_custom(form.name.data)
            db.session.commit()
            flash('Category added.', 'success')
            return redirect(url_for('settings.categories'))
        except ValueError as e:
            flash(str(e), 'error')

    all_categories = category_service.get_all_active()
    return render_template(
        'settings/categories.html',
        categories=all_categories,
        form=form,
        active_page='settings',
    )


@settings_bp.route('/categories/<int:category_id>/edit', methods=['GET', 'POST'])
def category_edit(category_id):
    cat = Category.query.get_or_404(category_id)
    form = CategoryForm(obj=cat)
    if form.validate_on_submit():
        try:
            category_service.rename(cat, form.name.data)
            db.session.commit()
            flash('Category updated.', 'success')
            return redirect(url_for('settings.categories'))
        except ValueError as e:
            flash(str(e), 'error')

    return render_template(
        'settings/category_edit.html',
        form=form,
        category=cat,
        active_page='settings',
    )


@settings_bp.route('/backup')
def backup():
    """NFR-3 — Download the SQLite database file as a backup."""
    db_uri = current_app.config.get('SQLALCHEMY_DATABASE_URI', '')
    if db_uri.startswith('sqlite:///'):
        # Relative path
        db_path = os.path.join(current_app.root_path, '..', db_uri[len('sqlite:///'):])
    elif db_uri.startswith('sqlite:////'):
        # Absolute path
        db_path = db_uri[len('sqlite:////'):]
    else:
        flash('Database backup is only available for SQLite databases.', 'error')
        return redirect(url_for('settings.index'))

    db_path = os.path.normpath(db_path)
    if not os.path.exists(db_path):
        flash('Database file not found.', 'error')
        return redirect(url_for('settings.index'))

    from datetime import date
    filename = f"financials_backup_{date.today().isoformat()}.db"
    return send_file(db_path, as_attachment=True, download_name=filename,
                     mimetype='application/octet-stream')


@settings_bp.route('/categories/<int:category_id>/delete', methods=['POST'])
def category_delete(category_id):
    cat = Category.query.get_or_404(category_id)
    try:
        category_service.soft_delete(cat)
        db.session.commit()
        flash('Category deleted.', 'success')
    except ValueError as e:
        msg = str(e)
        if 'System' in msg:
            abort(403)
        flash(msg, 'error')
    return redirect(url_for('settings.categories'))
