"""New content in unchanged format-12 tables; original source facts survive."""
import hashlib
import json
import shutil
import uuid
import zipfile
from pathlib import Path
import sqlite3

from fastapi.testclient import TestClient
from backend import recovery
from backend.app import create_app
from test_boundaries import client, ORIGIN
from test_public_enquiries import payload, photo, submit, saved
from test_photos import listing, state, share, project, pub
from test_recovery import table_rows, new_login, rewrite_archive
from photo_format_fixtures import heif, png_exact_size


def run_format_drill(tmp_path):
    source = tmp_path / 'fictional-formats'
    app = create_app(source)
    records = {}
    for index, (bid, slug) in enumerate([('oak', 'oak-and-vale'), ('harbour', 'harbour-homeworks')]):
        owner = client(app, index)
        data = payload(photos=[*[photo(raw=heif(kind), name=kind+'.heic') for kind in ['rotated', 'grid', '10bit', '12bit']],
                               photo(raw=png_exact_size(10*1024*1024), name='ten-mebibyte.png')])
        key = str(uuid.uuid4()); path = '/api/public/'+slug+'/enquiries'
        job = saved(owner, submit(app, data, key, path))
        photos = listing(owner, job).json()
        assert state(owner, job, photos[0], 'review').status_code == 200
        assert share(owner, job, photos[0]).status_code == 200
        gallery = project(owner, job, photos[0], slug='synthetic-formats')
        assert pub(owner, gallery).status_code == 200
        records[bid] = {'data': data, 'key': key, 'path': path, 'job': job, 'gallery': gallery,
                        'cookies': dict(owner.cookies), 'tables': table_rows(source, bid),
                        'photos': {p.name: p.read_bytes() for p in (source/bid/'photos').iterdir()}}
        with app.state.store.tenant(bid) as db: db.execute('DELETE FROM operations')
    snapshot = recovery.backup(source, tmp_path/'formats.zip')
    assert recovery.verify(snapshot['archive'], snapshot['sha256'])['valid']
    with zipfile.ZipFile(snapshot['archive']) as z:
        meta = json.loads(z.read('manifest.json'))
        assert meta['version'] == 14 and meta['prototype_version'] == '0.25.0'
    shutil.rmtree(source)
    restored_root = tmp_path/'restored-formats'
    recovery.restore(snapshot['archive'], snapshot['sha256'], restored_root, confirm_fresh=True)
    restored = create_app(restored_root)
    for bid, r in records.items():
        after = table_rows(restored_root, bid)
        for table in ('enquiry_intakes', 'enquiry_photo_sources', 'photo_blobs', 'jobs'):
            assert after[table] == r['tables'][table], (bid, table)
        # Recovery deliberately revokes legacy portal grants. Original contact
        # facts stay identical while that separate access generation advances.
        before_customers = {row['id']: row for row in r['tables']['customers']}
        for customer in after['customers']:
            before = before_customers[customer['id']]
            assert customer == {**before, 'portal_hash': None, 'portal_version': before['portal_version'] + 1}
        assert {p.name: p.read_bytes() for p in (restored_root/bid/'photos').iterdir()} == r['photos']
        old = TestClient(restored, headers=ORIGIN); old.cookies.update(r['cookies'])
        assert old.get('/api/workspace').status_code == 401
        owner, _ = new_login(restored, restored_root, bid)
        current = owner.get('/api/jobs/'+r['job']['id']).json()
        assert current['enquiry_submission'] == r['job']['enquiry_submission']
        photos = listing(owner, current).json()
        assert len(photos) == 5 and all(not p['reviewed'] and p['customer_share'] is None for p in photos)
        assert pub(owner, r['gallery']).status_code == 409
        other = next(record for key, record in records.items() if key != bid)
        assert owner.get('/api/jobs/'+other['job']['id']).status_code == 404
        assert submit(restored, r['data'], r['key'], r['path']).json()['receipt'] == current['id']
        with restored.state.store.tenant(bid) as db:
            assert db.execute('SELECT count(*) FROM jobs').fetchone()[0] == 1
            assert db.execute('SELECT count(*) FROM photo_blobs').fetchone()[0] == 5
    second = recovery.backup(restored_root, tmp_path/'formats-second.zip')
    assert recovery.verify(second['archive'], second['sha256'])['valid']
    return {'prototype_version': '0.25.0', 'recovery_format': recovery.VERSION, 'enquiry_schema_change': False,
            'businesses': 2, 'genuinely_encoded_heif_photos': 8, 'full_10_MiB_pngs': 2,
            'source_dataset_deleted_before_restore': True, 'source_facts_hashes_names_order_and_byte_counts_preserved': True,
            'normalized_png_bytes_preserved': True, 'review_and_customer_sharing_suspended': True,
            'gallery_publication_withdrawn_and_republication_denied_before_review': True,
            'old_sessions_denied': True, 'foreign_business_denied': True,
            'retry_after_cache_pruning_and_restore_reuses_original_receipt': True,
            'second_generation_verified': True, 'browser_or_phone_verified': False,
            'snapshot_sha256': snapshot['sha256'], 'second_snapshot_sha256': second['sha256']}


def test_new_formats_disappearance_and_recovery(tmp_path):
    result = run_format_drill(tmp_path)
    (tmp_path/'enquiry-formats-recovery-drill.json').write_text(json.dumps(result, indent=2)+'\n')


def test_pinned_format12_schema_accepts_both_supported_original_metadata_versions(tmp_path):
    # The source is a real v0.23 format-12 snapshot. Never relabel a v13 DB as v12.
    fixture=Path(__file__).parent/'fixtures/v0.23-customer-moves-format12.zip'
    old=tmp_path/'format12-earlier-metadata.zip'
    def earlier_metadata(files,meta):
        assert meta['version']==12
        meta['prototype_version']='0.19.0'
    sha=rewrite_archive(fixture,old,earlier_metadata)
    assert recovery.verify(old,sha)['valid']
    recovery.restore(old,sha,tmp_path/'restored-old',confirm_fresh=True)
    with zipfile.ZipFile(fixture) as archive:
        source=tmp_path/'source.sqlite3';source.write_bytes(archive.read('businesses/oak/business.sqlite3'))
    with sqlite3.connect(source) as db:
        db.row_factory=sqlite3.Row
        original=[dict(r) for r in db.execute('SELECT * FROM enquiry_intakes ORDER BY rowid')]
    assert table_rows(tmp_path/'restored-old','oak')['enquiry_intakes']==original
