"""Offline only: download a commit-pinned open model and generate real embeddings.
No inference API, no private input, no browser model download.
"""
import hashlib
import importlib.metadata
import json
import os
import platform
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

import numpy as np
import onnxruntime as ort
from tokenizers import Tokenizer

ROOT = Path(__file__).resolve().parents[1]
MODEL = 'Xenova/all-MiniLM-L6-v2'
REVISION = '751bff37182d3f1213fa05d7196b954e230abad9'
CACHE = Path(os.environ.get('PORTFOLIO_MODEL_CACHE', '/tmp/portfolio-minilm-model'))
CACHE.mkdir(parents=True, exist_ok=True)

def sha(data): return hashlib.sha256(data).hexdigest()
def read(name): return json.loads((ROOT / 'demo' / name).read_text())
def download(name):
    target = CACHE / name
    target.parent.mkdir(parents=True, exist_ok=True)
    if not target.exists():
        print('Downloading', name, flush=True)
        request = urllib.request.Request(f'https://huggingface.co/{MODEL}/resolve/{REVISION}/{name}', headers={'User-Agent':'portfolio-offline-embedding'})
        with urllib.request.urlopen(request, timeout=120) as response:
            data = response.read()
        target.write_bytes(data)
    return target

model_path = download('onnx/model_quantized.onnx')
tokenizer_path = download('tokenizer.json')
tokenizer = Tokenizer.from_file(str(tokenizer_path))
tokenizer.enable_truncation(max_length=256)
tokenizer.enable_padding(pad_id=0, pad_token='[PAD]')
options = ort.SessionOptions()
options.intra_op_num_threads = 2
session = ort.InferenceSession(str(model_path), sess_options=options, providers=['CPUExecutionProvider'])

def embed(text):
    encoded = tokenizer.encode(text)
    values = {'input_ids': np.array([encoded.ids], dtype=np.int64), 'attention_mask': np.array([encoded.attention_mask], dtype=np.int64), 'token_type_ids': np.array([encoded.type_ids], dtype=np.int64)}
    output = session.run(None, {item.name:values[item.name] for item in session.get_inputs()})[0]
    mask = values['attention_mask'][..., None]
    vector = (output * mask).sum(axis=1) / mask.sum(axis=1)
    vector = vector[0] / np.linalg.norm(vector[0])
    return [round(float(x), 8) for x in vector]

documents, examples, dataset = read('documents.json'), read('queries.json'), read('eval-dataset.json')
chunk_vectors = {}
for doc in documents:
    for i, paragraph in enumerate(doc['content'].split('\n\n')):
        chunk_vectors[f"{doc['id']}#{i+1}"] = embed(doc['title']+'\n'+paragraph)
query_vectors = {q['id']: embed(q['question']) for q in examples}
eval_vectors = {q['id']: embed(q['query']) for q in dataset['cases']}
artifact = {
 'schemaVersion': 1,
 'provenance': {
  'model': MODEL, 'revision': REVISION, 'modelCard': 'https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2',
  'modelUrl':f'https://huggingface.co/{MODEL}/tree/{REVISION}', 'license':'Apache-2.0',
  'generatedAt':datetime.now(timezone.utc).isoformat(), 'dimensions':384,
  'pooling':'attention-mask mean pooling + L2 normalization', 'dtype':'quantized ONNX CPU',
  'chunkInput':'title + newline + paragraph', 'maxTokens':256,
  'modelSha256':sha(model_path.read_bytes()), 'tokenizerSha256':sha(tokenizer_path.read_bytes()),
  'documentsSha256':sha((ROOT/'demo/documents.json').read_bytes()),
  'documentsContentSha256':sha(json.dumps(documents,ensure_ascii=False,separators=(',',':')).encode()),
  'queriesContentSha256':sha(json.dumps(examples,ensure_ascii=False,separators=(',',':')).encode()),
  'queriesSha256':sha((ROOT/'demo/queries.json').read_bytes()),
  'datasetSha256':sha((ROOT/'demo/eval-dataset.json').read_bytes()),
  'generatorSha256':sha(Path(__file__).read_bytes()),
  'runtime':{'python':platform.python_version(), **{p:importlib.metadata.version(p) for p in ['numpy','onnxruntime','tokenizers']}},
 },
 'chunks': chunk_vectors, 'queries': query_vectors,
}
(ROOT/'demo/vectors.json').write_text(json.dumps(artifact, separators=(',',':'))+'\n')
(ROOT/'demo/eval-vectors.json').write_text(json.dumps({'datasetSha256':artifact['provenance']['datasetSha256'],'modelRevision':REVISION,'queries':eval_vectors},separators=(',',':'))+'\n')
print('Generated', len(chunk_vectors), 'chunk vectors,',len(query_vectors),'example vectors,',len(eval_vectors),'evaluation vectors; dimensions=384',flush=True)
