import os
import sqlite3
import unicodedata
from contextlib import contextmanager

DATA_DIR = os.environ.get('DATA_DIR', os.path.join(os.path.dirname(__file__), 'data'))
DATABASE_PATH = os.environ.get('DATABASE_PATH', os.path.join(DATA_DIR, 'site.db'))
_SCHEMA_PATH = os.path.join(os.path.dirname(__file__), 'schema.sql')


def _new_connection():
    os.makedirs(os.path.dirname(DATABASE_PATH) or '.', exist_ok=True)
    conn = sqlite3.connect(DATABASE_PATH)
    conn.row_factory = sqlite3.Row
    conn.execute('PRAGMA foreign_keys = ON')
    conn.execute('PRAGMA journal_mode = WAL')
    return conn


@contextmanager
def get_connection():
    conn = _new_connection()
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()


def _apply_schema(conn):
    with open(_SCHEMA_PATH, 'r', encoding='utf-8') as f:
        conn.executescript(f.read())


def _profile_is_empty(conn):
    row = conn.execute('SELECT COUNT(*) AS c FROM profile').fetchone()
    return row['c'] == 0


def _clean_tag_list(tags):
    out = []
    for t in tags or []:
        s = str(t).strip()
        if s:
            out.append(s)
    return out


def _tech_name_set(conn):
    return {
        str(r['name']).strip().lower()
        for r in conn.execute('SELECT name FROM techstack_item')
        if r['name'] and str(r['name']).strip()
    }


def _split_merged_tags(conn, merged):
    tech_set = _tech_name_set(conn)
    cat, tech = [], []
    for t in merged or []:
        s = str(t).strip()
        if not s:
            continue
        (tech if s.lower() in tech_set else cat).append(s)
    return cat, tech


def _project_tag_payload_from_body(conn, data):
    if 'techTags' in data:
        return _clean_tag_list(data.get('tags')), _clean_tag_list(data.get('techTags'))
    return _split_merged_tags(conn, data.get('tags') or [])


def _replace_project_tags(conn, project_id, tags):
    conn.execute('DELETE FROM project_tag WHERE project_id = ?', (project_id,))
    for j, tag in enumerate(_clean_tag_list(tags)):
        conn.execute(
            'INSERT INTO project_tag (project_id, sort_order, tag) VALUES (?, ?, ?)',
            (project_id, j, tag),
        )


def _replace_project_tech_tags(conn, project_id, tags):
    conn.execute('DELETE FROM project_tech_tag WHERE project_id = ?', (project_id,))
    for j, tag in enumerate(_clean_tag_list(tags)):
        conn.execute(
            'INSERT INTO project_tech_tag (project_id, sort_order, tag) VALUES (?, ?, ?)',
            (project_id, j, tag),
        )


def _migrate_legacy_project_tags_if_needed(conn):
    tech_set = _tech_name_set(conn)
    for row in conn.execute('SELECT id FROM project'):
        pid = row['id']
        if conn.execute(
            'SELECT 1 FROM project_tech_tag WHERE project_id = ? LIMIT 1',
            (pid,),
        ).fetchone():
            continue
        tag_rows = conn.execute(
            'SELECT tag FROM project_tag WHERE project_id = ? ORDER BY sort_order, id',
            (pid,),
        ).fetchall()
        if not tag_rows:
            continue
        cat, tech = [], []
        for r in tag_rows:
            s = str(r['tag']).strip()
            if not s:
                continue
            if s.lower() in tech_set:
                tech.append(s)
            else:
                cat.append(s)
        if not tech:
            continue
        conn.execute('DELETE FROM project_tag WHERE project_id = ?', (pid,))
        for j, t in enumerate(cat):
            conn.execute(
                'INSERT INTO project_tag (project_id, sort_order, tag) VALUES (?,?,?)',
                (pid, j, t),
            )
        for j, t in enumerate(tech):
            conn.execute(
                'INSERT INTO project_tech_tag (project_id, sort_order, tag) VALUES (?,?,?)',
                (pid, j, t),
            )


def _seed_minimal_defaults(conn):
    """空库时写入 profile 与技术栈区块占位行，内容通过管理后台或 SQL 维护。"""
    conn.execute('INSERT INTO profile (id, nickname) VALUES (1, ?)', ('',))
    conn.execute('INSERT INTO techstack_section (id, title) VALUES (1, ?)', ('技术栈',))


def _ensure_builtin_tech_items(conn):
    """内置技术栈项：按名称幂等追加（无则插入）。"""
    builtin = (
        (
            'Vite',
            'https://vitejs.dev/',
            'https://pan.shumengya.top/d/smy/image/devkit/vite.svg',
            '#646CFF',
        ),
        (
            'Tailwind CSS',
            'https://tailwindcss.com/',
            'https://pan.shumengya.top/d/smy/image/devkit/tailwind.svg',
            '#06B6D4',
        ),
        (
            'RabbitMQ',
            'https://www.rabbitmq.com/',
            'https://pan.shumengya.top/d/smy/image/devkit/rabbitmq.svg',
            '#FF6600',
        ),
    )
    for name, link, svg, color in builtin:
        if conn.execute(
            'SELECT 1 FROM techstack_item WHERE name = ? LIMIT 1',
            (name,),
        ).fetchone():
            continue
        m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM techstack_item').fetchone()['m']
        sort_order = int(m) + 10
        conn.execute(
            '''INSERT INTO techstack_item (sort_order, name, link, svg, color, is_show)
               VALUES (?, ?, ?, ?, ?, ?)''',
            (sort_order, name, link, svg, color, 1),
        )


def _ensure_contact_is_show_column(conn):
    """旧库 contact 表无 is_show 时追加列（默认展示）。"""
    cols = {str(r['name']) for r in conn.execute('PRAGMA table_info(contact)').fetchall()}
    if cols and 'is_show' not in cols:
        conn.execute(
            'ALTER TABLE contact ADD COLUMN is_show INTEGER NOT NULL DEFAULT 1'
        )


def init_storage():
    with get_connection() as conn:
        _apply_schema(conn)
        _ensure_contact_is_show_column(conn)
        if _profile_is_empty(conn):
            _seed_minimal_defaults(conn)
        else:
            _migrate_legacy_project_tags_if_needed(conn)
        _ensure_builtin_tech_items(conn)
        _apply_tech_item_display_patches(conn)
        _purge_retired_tech_labels(conn)


def _normalize_tech_label_key(s):
    """仅保留字母数字并小写；NFKC 合并全角/兼容字形，匹配 d1SQL 各类写法。"""
    if s is None:
        return ''
    t = unicodedata.normalize('NFKC', str(s)).strip()
    return ''.join(c.lower() for c in t if c.isalnum())


def _purge_retired_tech_labels(conn):
    """移除已废弃的技术名：项目标签 + 技术栈表（幂等）。"""
    retired_keys = {'d1sql'}
    for table in ('project_tech_tag', 'project_tag'):
        rows = conn.execute(f'SELECT id, tag FROM {table}').fetchall()
        for r in rows:
            if _normalize_tech_label_key(r['tag']) in retired_keys:
                conn.execute(f'DELETE FROM {table} WHERE id = ?', (r['id'],))
    rows = conn.execute('SELECT id, name FROM techstack_item').fetchall()
    for r in rows:
        if _normalize_tech_label_key(r['name']) in retired_keys:
            conn.execute('DELETE FROM techstack_item WHERE id = ?', (r['id'],))


def _apply_tech_item_display_patches(conn):
    """微调技术栈徽章底色（图标为自带颜色的 SVG 时避免与底色撞色）。"""
    patches = (
        # Docker.svg 为蓝色填充：深蓝→亮蓝渐变，与白字、鲸鱼标层次更清晰
        (
            'Docker',
            'linear-gradient(135deg, #052e4a 0%, #0d47a1 42%, #1e88e5 100%)',
        ),
        # Flask.svg 为 #3babc3 青色，避免纯黑底；深青绿过渡到品牌青
        (
            'Flask',
            'linear-gradient(135deg, #0f3a40 0%, #1e6570 45%, #2d8fa0 100%)',
        ),
        # JSON / Markdown：Simple Icons 等品牌资料中的标准色（实心）
        # JSON: https://simpleicons.org — #000000（与 Crockford JSON 标识一致）
        ('JSON', '#000000'),
        # Markdown: Markdown 标识常用主色 — #083FA5
        ('Markdown', '#083FA5'),
    )
    for name, color in patches:
        conn.execute(
            'UPDATE techstack_item SET color = ? WHERE name = ?',
            (color, name),
        )


def _row_to_profile(row):
    if row is None:
        return None
    keys = ('favicon', 'nickname', 'avatar', 'introduction', 'footer', 'site', 'homepage')
    return {k: row[k] for k in keys if row[k] is not None}


def get_profile():
    with get_connection() as conn:
        row = conn.execute(
            '''SELECT favicon, nickname, avatar, introduction, footer, site, homepage
               FROM profile WHERE id = 1'''
        ).fetchone()
        return _row_to_profile(row)


def get_projects():
    with get_connection() as conn:
        rows = conn.execute(
            'SELECT id, title, description, link, icon, is_admin, is_show, is_develop FROM project ORDER BY sort_order, id'
        ).fetchall()
        out = []
        for row in rows:
            tags = [
                r['tag']
                for r in conn.execute(
                    'SELECT tag FROM project_tag WHERE project_id = ? ORDER BY sort_order, id',
                    (row['id'],),
                )
            ]
            tech_tags = [
                r['tag']
                for r in conn.execute(
                    'SELECT tag FROM project_tech_tag WHERE project_id = ? ORDER BY sort_order, id',
                    (row['id'],),
                )
            ]
            out.append({
                'id': row['id'],
                'title': row['title'],
                'description': row['description'] or '',
                'link': row['link'] or '',
                'icon': row['icon'] or '',
                'tags': tags,
                'techTags': tech_tags,
                'admin': bool(row['is_admin']),
                'show': bool(row['is_show']),
                'develop': bool(row['is_develop']),
            })
        return {'projects': out}


def _contact_row_to_dict(row):
    return {
        'id': row['id'],
        'type': row['type'],
        'label': row['label'] or '',
        'value': row['value'] or '',
        'link': row['link'] or '',
        'icon': row['icon'] or '',
        'show': bool(row['is_show']),
    }


def get_contacts():
    """前台公开接口：仅返回首页展示的联系方式。"""
    with get_connection() as conn:
        rows = conn.execute(
            '''SELECT id, type, label, value, link, icon, is_show FROM contact
               WHERE COALESCE(is_show, 1) = 1
               ORDER BY sort_order, id'''
        ).fetchall()
        return {
            'contacts': [_contact_row_to_dict(row) for row in rows],
        }


def get_techstack():
    """技术栈 svg 字段：可为 data/logo 下文件名，或 http(s) 外链 SVG 地址。"""
    with get_connection() as conn:
        sec = conn.execute('SELECT title FROM techstack_section WHERE id = 1').fetchone()
        title = sec['title'] if sec else '技术栈'
        rows = conn.execute(
            'SELECT id, name, link, svg, color, is_show FROM techstack_item ORDER BY sort_order, id'
        ).fetchall()
        return {
            'title': title,
            'items': [
                {
                    'id': row['id'],
                    'name': row['name'],
                    'link': row['link'] or '',
                    'svg': row['svg'] or '',
                    'color': row['color'] or '',
                    'show': bool(row['is_show']),
                }
                for row in rows
            ],
        }


def get_all(include_hidden_contacts=False):
    with get_connection() as conn:
        profile_row = conn.execute(
            '''SELECT favicon, nickname, avatar, introduction, footer, site, homepage
               FROM profile WHERE id = 1'''
        ).fetchone()
        profile = _row_to_profile(profile_row)

        tech_sec = conn.execute('SELECT title FROM techstack_section WHERE id = 1').fetchone()
        tech_title = tech_sec['title'] if tech_sec else '技术栈'
        tech_rows = conn.execute(
            'SELECT id, name, link, svg, color, is_show FROM techstack_item ORDER BY sort_order, id'
        ).fetchall()
        techstack = {
            'title': tech_title,
            'items': [
                {
                    'id': r['id'],
                    'name': r['name'],
                    'link': r['link'] or '',
                    'svg': r['svg'] or '',
                    'color': r['color'] or '',
                    'show': bool(r['is_show']),
                }
                for r in tech_rows
            ],
        }

        proj_rows = conn.execute(
            'SELECT id, title, description, link, icon, is_admin, is_show, is_develop FROM project ORDER BY sort_order, id'
        ).fetchall()
        projects = []
        for row in proj_rows:
            tags = [
                r['tag']
                for r in conn.execute(
                    'SELECT tag FROM project_tag WHERE project_id = ? ORDER BY sort_order, id',
                    (row['id'],),
                )
            ]
            tech_tags = [
                r['tag']
                for r in conn.execute(
                    'SELECT tag FROM project_tech_tag WHERE project_id = ? ORDER BY sort_order, id',
                    (row['id'],),
                )
            ]
            projects.append({
                'id': row['id'],
                'title': row['title'],
                'description': row['description'] or '',
                'link': row['link'] or '',
                'icon': row['icon'] or '',
                'tags': tags,
                'techTags': tech_tags,
                'admin': bool(row['is_admin']),
                'show': bool(row['is_show']),
                'develop': bool(row['is_develop']),
            })

        contact_rows = conn.execute(
            'SELECT id, type, label, value, link, icon, is_show FROM contact ORDER BY sort_order, id'
        ).fetchall()
        contact_list = []
        for cr in contact_rows:
            if not include_hidden_contacts and not bool(cr['is_show']):
                continue
            contact_list.append(_contact_row_to_dict(cr))
        contacts = {'contacts': contact_list}

        return {
            'profile': profile,
            'techstack': techstack,
            'projects': {'projects': projects},
            'contacts': contacts,
        }


def update_profile(payload):
    allowed = {'favicon', 'nickname', 'avatar', 'introduction', 'footer', 'site', 'homepage'}
    updates = {k: payload[k] for k in allowed if k in payload}
    if not updates:
        return
    sets = ', '.join(f'{k} = ?' for k in updates)
    vals = list(updates.values())
    with get_connection() as conn:
        conn.execute(f'UPDATE profile SET {sets} WHERE id = 1', vals)


def create_project(data):
    with get_connection() as conn:
        cat_tags, tech_tags = _project_tag_payload_from_body(conn, data)
        m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM project').fetchone()['m']
        sort_order = int(m) + 10
        cur = conn.execute(
            '''INSERT INTO project (sort_order, title, description, link, icon, is_admin, is_show, is_develop)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
            (
                sort_order,
                data['title'],
                data.get('description') or '',
                data.get('link') or '',
                data.get('icon') or '',
                1 if data.get('admin') else 0,
                1 if data.get('show', True) else 0,
                1 if data.get('develop') else 0,
            ),
        )
        pid = cur.lastrowid
        _replace_project_tags(conn, pid, cat_tags)
        _replace_project_tech_tags(conn, pid, tech_tags)
        return pid


def update_project(project_id, data):
    with get_connection() as conn:
        fields = []
        vals = []
        if 'title' in data:
            fields.append('title = ?')
            vals.append(data['title'])
        if 'description' in data:
            fields.append('description = ?')
            vals.append(data['description'] or '')
        if 'link' in data:
            fields.append('link = ?')
            vals.append(data['link'] or '')
        if 'icon' in data:
            fields.append('icon = ?')
            vals.append(data['icon'] or '')
        if 'admin' in data:
            fields.append('is_admin = ?')
            vals.append(1 if data['admin'] else 0)
        if 'show' in data:
            fields.append('is_show = ?')
            vals.append(1 if data['show'] else 0)
        if 'develop' in data:
            fields.append('is_develop = ?')
            vals.append(1 if data['develop'] else 0)
        if fields:
            vals.append(project_id)
            conn.execute(
                f"UPDATE project SET {', '.join(fields)} WHERE id = ?",
                vals,
            )
        if 'tags' in data or 'techTags' in data:
            if 'tags' in data and 'techTags' in data:
                _replace_project_tags(conn, project_id, data['tags'])
                _replace_project_tech_tags(conn, project_id, data['techTags'])
            elif 'tags' in data:
                cat, tech = _split_merged_tags(conn, data['tags'])
                _replace_project_tags(conn, project_id, cat)
                _replace_project_tech_tags(conn, project_id, tech)
            else:
                _replace_project_tech_tags(conn, project_id, data['techTags'])


def delete_project(project_id):
    with get_connection() as conn:
        conn.execute('DELETE FROM project WHERE id = ?', (project_id,))


def create_contact(data):
    with get_connection() as conn:
        m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM contact').fetchone()['m']
        sort_order = int(m) + 10
        cur = conn.execute(
            '''INSERT INTO contact (sort_order, type, label, value, link, icon, is_show)
               VALUES (?, ?, ?, ?, ?, ?, ?)''',
            (
                sort_order,
                data['type'],
                data.get('label') or '',
                data.get('value') or '',
                data.get('link') or '',
                data.get('icon') or '',
                1 if data.get('show', True) else 0,
            ),
        )
        return cur.lastrowid


def update_contact(contact_id, data):
    with get_connection() as conn:
        fields = []
        vals = []
        for col, key in (
            ('type', 'type'),
            ('label', 'label'),
            ('value', 'value'),
            ('link', 'link'),
            ('icon', 'icon'),
        ):
            if key in data:
                fields.append(f'{col} = ?')
                vals.append(data[key] or '')
        if 'show' in data:
            fields.append('is_show = ?')
            vals.append(1 if data['show'] else 0)
        if fields:
            vals.append(contact_id)
            conn.execute(
                f"UPDATE contact SET {', '.join(fields)} WHERE id = ?",
                vals,
            )


def delete_contact(contact_id):
    with get_connection() as conn:
        conn.execute('DELETE FROM contact WHERE id = ?', (contact_id,))


def set_techstack_title(title):
    t = title or '技术栈'
    with get_connection() as conn:
        row = conn.execute('SELECT id FROM techstack_section WHERE id = 1').fetchone()
        if row:
            conn.execute('UPDATE techstack_section SET title = ? WHERE id = 1', (t,))
        else:
            conn.execute('INSERT INTO techstack_section (id, title) VALUES (1, ?)', (t,))


def create_techstack_item(data):
    with get_connection() as conn:
        m = conn.execute('SELECT COALESCE(MAX(sort_order), 0) AS m FROM techstack_item').fetchone()['m']
        sort_order = int(m) + 10
        cur = conn.execute(
            '''INSERT INTO techstack_item (sort_order, name, link, svg, color, is_show)
               VALUES (?, ?, ?, ?, ?, ?)''',
            (
                sort_order,
                data['name'],
                data.get('link') or '',
                data.get('svg') or '',
                data.get('color') or '',
                1 if data.get('show', True) else 0,
            ),
        )
        return cur.lastrowid


def update_techstack_item(item_id, data):
    with get_connection() as conn:
        fields = []
        vals = []
        if 'name' in data:
            fields.append('name = ?')
            vals.append(data['name'])
        if 'link' in data:
            fields.append('link = ?')
            vals.append(data['link'] or '')
        if 'svg' in data:
            fields.append('svg = ?')
            vals.append(data['svg'] or '')
        if 'color' in data:
            fields.append('color = ?')
            vals.append(data['color'] or '')
        if 'show' in data:
            fields.append('is_show = ?')
            vals.append(1 if data['show'] else 0)
        if fields:
            vals.append(item_id)
            conn.execute(
                f"UPDATE techstack_item SET {', '.join(fields)} WHERE id = ?",
                vals,
            )


def delete_techstack_item(item_id):
    with get_connection() as conn:
        conn.execute('DELETE FROM techstack_item WHERE id = ?', (item_id,))
