"""
Transaction edit and delete tests — Story 3.4.
"""
import hashlib
from decimal import Decimal
import pytest
from app.models.account import Account
from app.models.category import Category
from app.models.transaction import Transaction
from app.extensions import db as _db


# ── Fixtures ──────────────────────────────────────────────────────────────────

@pytest.fixture()
def acct(db):
    a = Account(name='Chase', type='checking', is_active=True)
    _db.session.add(a)
    _db.session.commit()
    return a


@pytest.fixture()
def cat(db):
    c = Category(name='Groceries', is_system=True, is_active=True)
    _db.session.add(c)
    _db.session.commit()
    return c


@pytest.fixture()
def txn(db, acct, cat):
    def _hash(merchant, amount, date_str):
        amt = str(Decimal(str(amount)).quantize(Decimal('0.01')))
        return hashlib.sha256((merchant + amt + date_str).encode()).hexdigest()

    t = Transaction(
        date='2026-05-15',
        merchant_normalized='ALDI',
        merchant_raw='ALDI',
        amount=Decimal('29.99'),
        account_id=acct.id,
        category_id=cat.id,
        is_manual=True,
        dedup_hash=_hash('ALDI', '29.99', '2026-05-15'),
    )
    _db.session.add(t)
    _db.session.commit()
    return t


# ── Edit GET ──────────────────────────────────────────────────────────────────

class TestEditGet:
    def test_get_returns_200(self, client, db, txn):
        response = client.get(f'/transactions/{txn.id}/edit')
        assert response.status_code == 200

    def test_form_prefilled_with_merchant(self, client, db, txn):
        response = client.get(f'/transactions/{txn.id}/edit')
        assert b'ALDI' in response.data

    def test_form_prefilled_with_amount(self, client, db, txn):
        response = client.get(f'/transactions/{txn.id}/edit')
        assert b'29.99' in response.data

    def test_nonexistent_returns_404(self, client, db):
        response = client.get('/transactions/9999/edit')
        assert response.status_code == 404


# ── Edit POST ─────────────────────────────────────────────────────────────────

class TestEditPost:
    def test_valid_post_updates_transaction(self, client, db, txn, acct, cat):
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-20',
            'merchant': 'Whole Foods',
            'amount': '55.00',
            'category_id': cat.id,
            'account_id': acct.id,
            'notes': '',
            'force_save': '0',
        }, follow_redirects=False)
        assert response.status_code == 302
        _db.session.refresh(txn)
        assert txn.merchant_normalized == 'Whole Foods'
        assert txn.date == '2026-05-20'
        assert txn.amount == Decimal('55.00')

    def test_edit_recomputes_dedup_hash(self, client, db, txn, acct, cat):
        client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-20',
            'merchant': 'Whole Foods',
            'amount': '55.00',
            'category_id': cat.id,
            'account_id': acct.id,
            'notes': '',
            'force_save': '0',
        })
        _db.session.refresh(txn)
        amt = str(Decimal('55.00').quantize(Decimal('0.01')))
        expected = hashlib.sha256(('Whole Foods' + amt + '2026-05-20').encode()).hexdigest()
        assert txn.dedup_hash == expected

    def test_edit_redirects_to_list(self, client, db, txn, acct, cat):
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-20', 'merchant': 'Shop', 'amount': '10.00',
            'category_id': 0, 'account_id': acct.id, 'notes': '', 'force_save': '0',
        }, follow_redirects=False)
        assert '/transactions/' in response.headers['Location']

    def test_edit_flashes_success(self, client, db, txn, acct, cat):
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-20', 'merchant': 'Shop', 'amount': '10.00',
            'category_id': 0, 'account_id': acct.id, 'notes': '', 'force_save': '0',
        }, follow_redirects=True)
        assert b'Transaction updated' in response.data

    def test_invalid_amount_rerenders_form(self, client, db, txn, acct):
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-20', 'merchant': 'Shop', 'amount': 'bad',
            'category_id': 0, 'account_id': acct.id, 'notes': '', 'force_save': '0',
        })
        assert response.status_code == 200
        _db.session.refresh(txn)
        assert txn.merchant_normalized == 'ALDI'  # unchanged

    def test_nonexistent_returns_404(self, client, db, acct):
        response = client.post('/transactions/9999/edit', data={
            'date': '2026-05-20', 'merchant': 'Shop', 'amount': '10.00',
            'category_id': 0, 'account_id': acct.id, 'notes': '', 'force_save': '0',
        })
        assert response.status_code == 404


# ── Edit duplicate detection ──────────────────────────────────────────────────

class TestEditDuplicate:
    def test_editing_to_duplicate_shows_warning(self, client, db, txn, acct, cat):
        # Seed a second transaction that the edit will collide with
        other = Transaction(
            date='2026-05-15', merchant_normalized='Target',
            merchant_raw='Target', amount=Decimal('15.00'),
            account_id=acct.id, is_manual=True,
            dedup_hash=hashlib.sha256(
                ('Target' + '15.00' + '2026-05-15').encode()).hexdigest(),
        )
        _db.session.add(other)
        _db.session.commit()
        # Try to edit txn to match other's hash
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-15', 'merchant': 'Target', 'amount': '15.00',
            'category_id': cat.id, 'account_id': acct.id,
            'notes': '', 'force_save': '0',
        })
        assert response.status_code == 200
        assert b'duplicate' in response.data.lower()

    def test_editing_does_not_flag_itself(self, client, db, txn, acct, cat):
        # Saving with no changes should not trigger duplicate warning
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-15', 'merchant': 'ALDI', 'amount': '29.99',
            'category_id': cat.id, 'account_id': acct.id,
            'notes': '', 'force_save': '0',
        }, follow_redirects=False)
        assert response.status_code == 302  # redirect, not re-render

    def test_force_save_on_edit_saves_anyway(self, client, db, txn, acct, cat):
        other = Transaction(
            date='2026-05-15', merchant_normalized='Target',
            merchant_raw='Target', amount=Decimal('15.00'),
            account_id=acct.id, is_manual=True,
            dedup_hash=hashlib.sha256(
                ('Target' + '15.00' + '2026-05-15').encode()).hexdigest(),
        )
        _db.session.add(other)
        _db.session.commit()
        response = client.post(f'/transactions/{txn.id}/edit', data={
            'date': '2026-05-15', 'merchant': 'Target', 'amount': '15.00',
            'category_id': cat.id, 'account_id': acct.id,
            'notes': '', 'force_save': '1',
        }, follow_redirects=False)
        assert response.status_code == 302
        _db.session.refresh(txn)
        assert txn.merchant_normalized == 'Target'


# ── Delete ────────────────────────────────────────────────────────────────────

class TestDelete:
    def test_delete_hard_deletes_transaction(self, client, db, txn):
        txn_id = txn.id
        response = client.post(f'/transactions/{txn_id}/delete',
                               follow_redirects=False)
        assert response.status_code == 302
        assert Transaction.query.get(txn_id) is None

    def test_delete_redirects_to_list(self, client, db, txn):
        response = client.post(f'/transactions/{txn.id}/delete',
                               follow_redirects=False)
        assert '/transactions/' in response.headers['Location']

    def test_delete_flashes_success(self, client, db, txn):
        response = client.post(f'/transactions/{txn.id}/delete',
                               follow_redirects=True)
        assert b'Transaction deleted' in response.data

    def test_delete_nonexistent_returns_404(self, client, db):
        response = client.post('/transactions/9999/delete')
        assert response.status_code == 404
