"""허용 목록에 있는 공개용 사본만 읽는 자료실. 로컬 파일 실행/업로드 기능 없음."""
import csv
import json
import re
from functools import lru_cache
from itertools import islice
from pathlib import Path
from flask import Blueprint, abort, current_app, render_template, request, send_file

bp = Blueprint('materials', __name__, url_prefix='/materials')
LABELS = {'code': '프로그램·코드', 'map': '결과 지도', 'image': '그래프', 'csv': 'CSV 데이터', 'text': '문서·자료'}
PREVIEW_LIMIT = 240_000


@lru_cache(maxsize=8)
def load_catalog(directory):
    root = Path(directory).resolve()
    manifest = root / 'manifest.json'
    if not manifest.is_file():
        return root, {}, ''
    data = json.loads(manifest.read_text(encoding='utf-8'))
    return root, {entry['id']: entry for entry in data['files']}, data['created']


def catalog():
    return load_catalog(str(current_app.config['MATERIALS_DIR']))


def selected(file_id):
    root, entries, _ = catalog()
    if not re.fullmatch(r'[0-9a-f]{20}', file_id) or file_id not in entries:
        abort(404)
    entry = entries[file_id]
    base = (root / 'files').resolve()
    path = (base / entry['path']).resolve()
    if not path.is_relative_to(base) or not path.is_file():
        abort(404)
    return entry, path


def size_label(value):
    return f'{value / 1024 / 1024:.1f} MB' if value >= 1024 * 1024 else f'{value / 1024:.1f} KB'


@bp.get('/')
def index():
    _, entries, created = catalog()
    files = list(entries.values())
    folders = sorted({str(Path(e['path']).parent).replace('\\', '/') for e in files})
    folder, query = request.args.get('folder', ''), request.args.get('q', '').strip()[:200]
    if folder and folder not in folders:
        abort(404)
    shown = [e for e in files if (not folder or str(Path(e['path']).parent).replace('\\', '/') == folder) and (not query or query.casefold() in e['path'].casefold())]
    return render_template('materials_index.html', files=shown, total=len(files), folders=folders, folder=folder, query=query, labels=LABELS, size_label=size_label, created=created)


@bp.get('/file/<file_id>')
def detail(file_id):
    entry, path = selected(file_id)
    source = request.args.get('view') == 'source'
    lines, rows, header, truncated = [], [], [], False
    if entry['kind'] == 'csv' and not source:
        with path.open(encoding='utf-8-sig', newline='') as stream:
            reader = csv.reader(stream)
            header = next(reader, [])
            rows = [[value[:500] for value in row] for row in islice(reader, 50)]
    elif entry['kind'] not in {'map', 'image'} or source and entry['kind'] == 'map':
        with path.open('rb') as stream:
            raw = stream.read(PREVIEW_LIMIT + 1)
        text = raw[:PREVIEW_LIMIT].decode('utf-8-sig', errors='replace')
        all_lines = text.splitlines()
        lines = all_lines[:3000]
        truncated = len(raw) > PREVIEW_LIMIT or len(all_lines) > 3000
    return render_template('materials_detail.html', entry=entry, labels=LABELS, size_label=size_label, source=source, lines=lines, header=header, rows=rows, truncated=truncated)


@bp.get('/download/<file_id>')
def download(file_id):
    entry, path = selected(file_id)
    return send_file(path, as_attachment=True, download_name=path.name, conditional=True, max_age=3600)


@bp.get('/image/<file_id>')
def image(file_id):
    entry, path = selected(file_id)
    if entry['kind'] != 'image':
        abort(404)
    return send_file(path, mimetype='image/png', conditional=True, max_age=3600)


@bp.get('/preview/<file_id>')
def preview(file_id):
    entry, path = selected(file_id)
    if entry['kind'] != 'map':
        abort(404)
    response = send_file(path, mimetype='text/html', conditional=True, max_age=3600)
    response.headers['Content-Security-Policy'] = (
        "sandbox allow-scripts; default-src 'none'; "
        "script-src 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://code.jquery.com https://unpkg.com; "
        "style-src 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://code.jquery.com https://unpkg.com https://netdna.bootstrapcdn.com; "
        "img-src data: https://*.tile.openstreetmap.fr https://*.tile.openstreetmap.org https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com; "
        "font-src data: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://netdna.bootstrapcdn.com; "
        "connect-src https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://code.jquery.com https://unpkg.com; "
        "base-uri 'none'; form-action 'none'; frame-ancestors 'self'"
    )
    return response
