import os
import sys
import types
import uuid
from pathlib import Path
from unittest.mock import MagicMock, patch

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

if 'fitz' not in sys.modules:
    sys.modules['fitz'] = types.ModuleType('fitz')

if 'openai' not in sys.modules:
    openai_mod = types.ModuleType('openai')
    openai_mod.OpenAI = type('OpenAIStub', (), {})
    sys.modules['openai'] = openai_mod

if 'google' not in sys.modules:
    google_mod = types.ModuleType('google')
    genai_mod = types.ModuleType('google.genai')
    google_mod.genai = genai_mod
    sys.modules['google'] = google_mod
    sys.modules['google.genai'] = genai_mod

if 'pymupdf4llm' not in sys.modules:
    pymod = types.ModuleType('pymupdf4llm')
    pymod.to_markdown = lambda pdf_path, **kwargs: '# Dummy'
    sys.modules['pymupdf4llm'] = pymod

if 'rapidocr_onnxruntime' not in sys.modules:
    ro_mod = types.ModuleType('rapidocr_onnxruntime')
    class RapidOCR:
        def __init__(self):
            pass
        def __call__(self, data):
            return ([], None)
    ro_mod.RapidOCR = RapidOCR
    sys.modules['rapidocr_onnxruntime'] = ro_mod

import app.services.ingestion as orch
from app.services.storage.supabase_client import (
    check_supabase_hash,
    start_uploader_worker,
    stop_uploader_worker,
    verify_table_exists,
)
from neo4j import GraphDatabase
import psycopg


def load_env_file() -> dict[str, str]:
    env_path = PROJECT_ROOT / '.env'
    parsed: dict[str, str] = {}
    for raw_line in env_path.read_text().splitlines():
        line = raw_line.strip()
        if not line or line.startswith('#') or '=' not in line:
            continue
        key, value = line.split('=', 1)
        key = key.strip()
        value = value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
            value = value[1:-1]
        elif value.endswith('"') and not value.startswith('"'):
            value = value[:-1]
        elif value.startswith('"') and not value.endswith('"'):
            value = value[1:]
        parsed[key] = value
    return parsed


def main() -> None:
    env = load_env_file()
    os.environ.update(env)

    neo4j_uri = env['NEO4J_URI']
    neo4j_user = env['NEO4J_USERNAME']
    neo4j_password = env['NEO4J_PASSWORD']
    db_url = env.get('DIRECT_URL') or env['DATABASE_URL']

    run_id = f"task6_{uuid.uuid4().hex}"
    fake_pdf = PROJECT_ROOT / 'scripts' / f'{run_id}.pdf'
    fake_pdf.write_bytes(b'%PDF-1.4\n%EOF')
    actual_hash = orch.get_md5_hash(str(fake_pdf))

    sample_markdown = """
# Task 6 Sample Paper

## Introduction
This introduction is relevant for the LLM and should remain.

## Methodology
This method section is also relevant for the LLM.

## References
Reference entry 1.
Reference entry 2.

## Appendix
Appendix content that should be filtered out from LLM context.
"""

    captured_llm_text = {'value': None}

    upload_queue, worker_thread = start_uploader_worker()
    print('Supabase uploader worker started')

    with GraphDatabase.driver(neo4j_uri, auth=(neo4j_user, neo4j_password)) as driver, \
         patch.object(orch, 'check_node_exists', return_value=False), \
         patch.object(orch.hybrid_parser, 'parse_pdf', return_value=sample_markdown), \
         patch.object(orch, 'extract_pillars_with_llm', side_effect=lambda text, client: captured_llm_text.__setitem__('value', text) or {
             'background': 'background evidence',
             'methodology': 'methodology evidence',
             'gap': 'gap evidence',
             'objective': 'objective evidence',
             'futurework': 'futurework evidence',
         }), \
         patch.object(orch, 'embed_text', return_value=[0.1] * 768):
        result = orch.process_sync_pdf(
            str(fake_pdf),
            driver=driver,
            or_client=MagicMock(),
            gemini_client=MagicMock(),
            upload_queue=upload_queue,
        )
        print(f'process_sync_pdf_result={result}')

    stop_uploader_worker(timeout=60)
    print('Supabase uploader worker stopped')

    if captured_llm_text['value'] is None:
        raise RuntimeError('LLM context was not captured')

    if 'References' in captured_llm_text['value'] or 'Appendix' in captured_llm_text['value']:
        raise RuntimeError('Filtered text still contains irrelevant sections')

    if len(captured_llm_text['value']) > 25000:
        raise RuntimeError('LLM text exceeded 25000 characters')

    if not result:
        raise RuntimeError('Neo4j node was not created')

    print(f'llm_text_length={len(captured_llm_text["value"])}')

    table_exists = verify_table_exists('document_chunks')
    print(f'table_exists={table_exists}')
    if not table_exists:
        raise RuntimeError('document_chunks table is not accessible')

    supabase_exists = check_supabase_hash(actual_hash)
    print(f'supabase_exists={supabase_exists}')
    if not supabase_exists:
        raise RuntimeError('Supabase rows were not written for the test hash')

    with psycopg.connect(db_url, connect_timeout=10) as conn:
        with conn.cursor() as cur:
            cur.execute('SELECT COUNT(*) FROM document_chunks WHERE file_hash = %s', (actual_hash,))
            row_count = cur.fetchone()[0]
        conn.commit()
    print(f'supabase_row_count={row_count}')
    if row_count <= 0:
        raise RuntimeError('Supabase row count is zero after ingest')

    with GraphDatabase.driver(neo4j_uri, auth=(neo4j_user, neo4j_password)) as driver:
        with driver.session() as session:
            node_count = session.run(
                'MATCH (n:Literature {hash: $hash_val}) RETURN count(n) AS c',
                hash_val=actual_hash,
            ).single()['c']
            session.run('MATCH (n:Literature {hash: $hash_val}) DETACH DELETE n', hash_val=actual_hash)
    print(f'neo4j_node_count={node_count}')
    if node_count <= 0:
        raise RuntimeError('Neo4j node count is zero after ingest')

    with psycopg.connect(db_url, connect_timeout=10) as conn:
        with conn.cursor() as cur:
            cur.execute('DELETE FROM document_chunks WHERE file_hash = %s', (actual_hash,))
            deleted_rows = cur.rowcount
        conn.commit()
    print(f'supabase_deleted_rows={deleted_rows}')

    final_supabase_exists = check_supabase_hash(actual_hash)
    print(f'final_supabase_exists={final_supabase_exists}')
    if final_supabase_exists:
        raise RuntimeError('Supabase cleanup failed')

    fake_pdf.unlink(missing_ok=True)
    print('Task 6 main flow test finished successfully')


if __name__ == '__main__':
    main()
