import json
import os
import sqlite3
import threading
import time
import uuid


class Storage:
    def __init__(self, path=None):
        self.path = path or os.path.join(os.getcwd(), ".gilas.db")
        self._lock = threading.Lock()
        self._conn = sqlite3.connect(self.path, check_same_thread=False)
        self._conn.execute("PRAGMA journal_mode=WAL;")
        self._conn.execute("PRAGMA synchronous=NORMAL;")
        self._conn.execute(
            "CREATE TABLE IF NOT EXISTS objects ("
            "id TEXT PRIMARY KEY, "
            "type TEXT NOT NULL, "
            "data TEXT NOT NULL, "
            "updated_at REAL NOT NULL"
            ")"
        )
        self._conn.execute(
            "CREATE TABLE IF NOT EXISTS names ("
            "name TEXT PRIMARY KEY, "
            "id TEXT NOT NULL, "
            "type TEXT NOT NULL, "
            "updated_at REAL NOT NULL"
            ")"
        )
        self._conn.commit()
        self._cache = {}
        self._load_cache()

    def _load_cache(self):
        cursor = self._conn.execute("SELECT id, type, data FROM objects")
        for object_id, object_type, data in cursor.fetchall():
            self._cache[object_id] = (object_type, json.loads(data))

    def generate_id(self):
        return uuid.uuid4().hex

    def get(self, object_id):
        with self._lock:
            cached = self._cache.get(object_id)
            if cached is not None:
                return cached

            cursor = self._conn.execute(
                "SELECT type, data FROM objects WHERE id = ?", (object_id,)
            )
            row = cursor.fetchone()
            if row is None:
                return None
            object_type, data = row
            parsed = json.loads(data)
            self._cache[object_id] = (object_type, parsed)
            return object_type, parsed

    def save(self, object_id, object_type, data):
        payload = json.dumps(data)
        now = time.time()
        with self._lock:
            self._cache[object_id] = (object_type, data)
            self._conn.execute(
                "INSERT INTO objects (id, type, data, updated_at) "
                "VALUES (?, ?, ?, ?) "
                "ON CONFLICT(id) DO UPDATE SET "
                "type=excluded.type, data=excluded.data, updated_at=excluded.updated_at",
                (object_id, object_type, payload, now),
            )
            self._conn.commit()

    def get_named(self, name, object_type):
        with self._lock:
            cursor = self._conn.execute(
                "SELECT id FROM names WHERE name = ? AND type = ?",
                (name, object_type),
            )
            row = cursor.fetchone()
            if row is None:
                return None
            return row[0]

    def set_named(self, name, object_id, object_type):
        now = time.time()
        with self._lock:
            self._conn.execute(
                "INSERT INTO names (name, id, type, updated_at) "
                "VALUES (?, ?, ?, ?) "
                "ON CONFLICT(name) DO UPDATE SET "
                "id=excluded.id, type=excluded.type, updated_at=excluded.updated_at",
                (name, object_id, object_type, now),
            )
            self._conn.commit()

    def delete(self, object_id):
        with self._lock:
            self._cache.pop(object_id, None)
            self._conn.execute("DELETE FROM objects WHERE id = ?", (object_id,))
            self._conn.commit()


_STORAGE = None
_STORAGE_PATH = None


def _resolve_path(path=None):
    resolved_path = path or os.environ.get("GILAS_DB_PATH")
    return resolved_path or os.path.join(os.getcwd(), ".gilas.db")


def get_storage(path=None):
    global _STORAGE, _STORAGE_PATH
    resolved_path = _resolve_path(path)
    if _STORAGE is None or resolved_path != _STORAGE_PATH:
        _STORAGE = Storage(resolved_path)
        _STORAGE_PATH = resolved_path
    return _STORAGE
