from __future__ import annotations

import sqlite3
from pathlib import Path

from jarvis_finance.storage.migrations import apply_migrations
from jarvis_finance.services.grocery_optimizer import (
    add_demo_migros_receipt,
    generate_grocery_optimization_report,
    get_grocery_optimizer_dashboard,
    normalize_product_name,
    run_grocery_optimization,
    upsert_product_match,
)


def db() -> sqlite3.Connection:
    conn = sqlite3.connect(':memory:')
    conn.row_factory = sqlite3.Row
    apply_migrations(conn)
    return conn


def test_migros_receipt_items_are_selectable_detail_rows_and_decimal_text() -> None:
    conn = db()
    receipt = add_demo_migros_receipt(conn, purchase_date='2026-05-19', store_name='Migros Test', items=[
        {'raw_product_name': 'M-Budget Milch 1L Aktion', 'quantity_text': '1 x 1L', 'unit': 'l', 'unit_price_text': '1.20', 'total_price_text': '1.20', 'action_label': 'Aktion'},
        {'raw_product_name': 'Bio Eier 6 Stück', 'quantity_text': '6 Stk', 'unit': 'stück', 'unit_price_text': '0.70', 'total_price_text': '4.20'},
    ])

    dashboard = get_grocery_optimizer_dashboard(conn)

    assert dashboard['purpose'] == 'migros_product_savings_grocery_optimizer_v1'
    assert dashboard['receipts'][0]['receipt_id'] == receipt['receipt_id']
    assert dashboard['receipts'][0]['article_count'] == 2
    assert dashboard['receipts'][0]['total_text'] == '5.40'
    first_item = dashboard['receipts'][0]['items'][0]
    assert first_item['raw_product_name'] == 'M-Budget Milch 1L Aktion'
    assert first_item['normalized_product_name'] == 'milch'
    assert first_item['normalization_confidence'] == '0.82'
    assert isinstance(first_item['total_price_text'], str)
    assert first_item['health_analysis_status'] == 'prepared_not_run'
    assert first_item['health_flags_json'] == '{}'


def test_normalization_preserves_original_and_stores_confidence() -> None:
    result = normalize_product_name('  AKTION! M-Budget Bio Joghurt 500g  ')
    assert result['raw_product_name'] == '  AKTION! M-Budget Bio Joghurt 500g  '
    assert result['normalized_product_name'] == 'joghurt'
    assert result['brand_hint'] == 'M-Budget'
    assert result['quantity_hint'] == '500g'
    assert result['confidence'] == '0.82'


def test_product_matching_marks_pack_size_and_excludes_uncertain_savings() -> None:
    conn = db()
    receipt = add_demo_migros_receipt(conn, purchase_date='2026-05-19', store_name='Migros Test', items=[
        {'raw_product_name': 'Milch 1L', 'quantity_text': '1L', 'unit': 'l', 'unit_price_text': '1.50', 'total_price_text': '1.50'},
        {'raw_product_name': 'Käse Spezial 200g', 'quantity_text': '200g', 'unit': 'g', 'unit_price_text': '4.00', 'total_price_text': '4.00'},
    ])
    milk_id = receipt['items'][0]['product_item_id']
    cheese_id = receipt['items'][1]['product_item_id']
    upsert_product_match(conn, milk_id, retailer='Coop', candidate_product_name='Prix Garantie Milch 1L', candidate_url='https://example.invalid/coop/milch', candidate_price_text='1.20', candidate_unit_price_text='1.20/l', quality_flags=['close_match'], match_confidence='0.86', status='suggested')
    upsert_product_match(conn, cheese_id, retailer='Aldi Suisse', candidate_product_name='Käse 400g', candidate_url='https://example.invalid/aldi/kaese', candidate_price_text='3.20', candidate_unit_price_text='8.00/kg', quality_flags=['different_pack_size', 'needs_review'], match_confidence='0.52', status='needs_review')

    run = run_grocery_optimization(conn, receipt['receipt_id'], selected_retailers=['Coop', 'Aldi Suisse', 'Lidl'], max_store_count=3)

    assert run['receipt_id'] == receipt['receipt_id']
    assert run['max_store_count'] == 3
    assert run['optimized_lists'][0]['store_count'] <= 3
    assert run['summary']['replaceable_product_count'] == 1
    assert run['summary']['uncertain_product_count'] == 1
    assert run['summary']['estimated_savings_text'] == '0.30'
    assert any(item['status'] == 'needs_review' for item in run['product_comparisons'])
    uncertain = [item for item in run['product_comparisons'] if item['migros_product'] == 'Käse Spezial 200g'][0]
    assert 'different_pack_size' in uncertain['quality_flags']
    assert uncertain['savings_text'] == '0.00'


def test_runtime_report_stays_outside_repo_and_contains_disclaimer(tmp_path: Path) -> None:
    conn = db()
    receipt = add_demo_migros_receipt(conn, purchase_date='2026-05-19', store_name='Migros Test', items=[
        {'raw_product_name': 'Milch 1L', 'quantity_text': '1L', 'unit': 'l', 'unit_price_text': '1.50', 'total_price_text': '1.50'},
    ])
    item_id = receipt['items'][0]['product_item_id']
    upsert_product_match(conn, item_id, retailer='Coop', candidate_product_name='Prix Garantie Milch 1L', candidate_url='https://example.invalid/coop/milch', candidate_price_text='1.20', candidate_unit_price_text='1.20/l', quality_flags=['close_match'], match_confidence='0.86', status='suggested')
    run = run_grocery_optimization(conn, receipt['receipt_id'], selected_retailers=['Coop'], max_store_count=1)

    report = generate_grocery_optimization_report(conn, run['run_id'], reports_dir=tmp_path / 'runtime-reports', repo_root=Path.cwd())

    assert report['format'] == 'markdown'
    report_path = Path(report['report_path'])
    assert report_path.exists()
    assert Path.cwd().resolve() not in report_path.resolve().parents
    text = report_path.read_text()
    assert 'Migros Einkaufsoptimierung' in text
    assert 'keine medizinische Empfehlung' in text
    assert 'Produktgleichheit nicht garantiert' in text


def test_report_rejects_repo_path(tmp_path: Path) -> None:
    conn = db()
    receipt = add_demo_migros_receipt(conn, purchase_date='2026-05-19', store_name='Migros Test', items=[{'raw_product_name': 'Milch', 'total_price_text': '1.50'}])
    run = run_grocery_optimization(conn, receipt['receipt_id'], selected_retailers=['Coop'], max_store_count=1)
    try:
        generate_grocery_optimization_report(conn, run['run_id'], reports_dir=Path.cwd() / 'reports', repo_root=Path.cwd())
    except ValueError as exc:
        assert 'reports_dir_must_be_outside_repo' in str(exc)
    else:
        raise AssertionError('repo report path accepted')


def test_optimizer_totals_respect_max_three_store_limit() -> None:
    conn = db()
    receipt = add_demo_migros_receipt(conn, purchase_date='2026-05-19', store_name='Migros Test', items=[
        {'raw_product_name': 'Milch', 'total_price_text': '2.00'},
        {'raw_product_name': 'Eier', 'total_price_text': '2.00'},
        {'raw_product_name': 'Reis', 'total_price_text': '2.00'},
        {'raw_product_name': 'Joghurt', 'total_price_text': '2.00'},
    ])
    for item, retailer in zip(receipt['items'], ['Coop', 'Aldi Suisse', 'Lidl', 'Denner']):
        upsert_product_match(conn, item['product_item_id'], retailer=retailer, candidate_product_name=f'{retailer} Alternative', candidate_url=f'https://example.invalid/{retailer}', candidate_price_text='1.00', candidate_unit_price_text='1.00/stk', quality_flags=['close_match'], match_confidence='0.80')

    run = run_grocery_optimization(conn, receipt['receipt_id'], selected_retailers=['Coop', 'Aldi Suisse', 'Lidl', 'Denner'], max_store_count=3)

    assert run['optimized_lists'][0]['store_count'] == 3
    assert run['optimized_total_text'] == '5.00'
    assert run['summary']['estimated_savings_text'] == '3.00'
    assert len(run['optimized_lists'][0]['stores']) == 3
    assert any(row['status'] == 'not_selected_due_to_store_limit' for row in run['product_comparisons'])


def test_optimizer_respects_included_product_filter() -> None:
    conn = db()
    receipt = add_demo_migros_receipt(conn, purchase_date='2026-05-19', store_name='Migros Test', items=[
        {'raw_product_name': 'Milch', 'total_price_text': '2.00'},
        {'raw_product_name': 'Eier', 'total_price_text': '4.00'},
    ])
    upsert_product_match(conn, receipt['items'][0]['product_item_id'], retailer='Coop', candidate_product_name='Coop Milch', candidate_url='https://example.invalid/coop/milch', candidate_price_text='1.00', candidate_unit_price_text='1.00/stk', quality_flags=['close_match'], match_confidence='0.80')
    run = run_grocery_optimization(conn, receipt['receipt_id'], selected_retailers=['Coop'], max_store_count=1, included_product_item_ids=[receipt['items'][0]['product_item_id']])
    assert run['original_total_text'] == '2.00'
    assert len(run['product_comparisons']) == 1
