"""
Local shader compiler implementation using SQLite database
"""
from enum import Enum
import re
from typing import Dict, List, Optional, Union
import hashlib
import json
import sqlite3
import threading
import logging
import os
from pathlib import Path

class ShaderType(Enum):
    VERTEX = "vertex"
    FRAGMENT = "fragment"
    COMPUTE = "compute"

class ShaderError(Exception):
    pass

class ShaderCompilerDB:
    """Local shader compiler database using SQLite"""
    
    def __init__(self, db_path: str = "db/graphics/shaders.db"):
        """Initialize shader compiler database"""
        self.db_path = db_path
        os.makedirs(os.path.dirname(db_path), exist_ok=True)
        self.lock = threading.Lock()
        self._connect()
        
    def _connect(self):
        """Establish database connection"""
        try:
            self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
            self._setup_database()
        except Exception as e:
            raise RuntimeError(f"Failed to initialize database: {str(e)}")

    def _setup_database(self):
        """Initialize database tables"""
        with self.lock:
            # Shader programs table
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS shader_programs (
                    program_id TEXT PRIMARY KEY,
                    shader_type TEXT NOT NULL,
                    source_code TEXT NOT NULL,
                    compiled_code BLOB,
                    metadata TEXT,
                    creation_time REAL,
                    last_used REAL,
                    validation_status TEXT,
                    error_log TEXT
                )
            """)
            
            # Shader uniforms table
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS shader_uniforms (
                    uniform_id TEXT PRIMARY KEY,
                    program_id TEXT,
                    name TEXT NOT NULL,
                    type TEXT NOT NULL,
                    location INTEGER,
                    metadata TEXT,
                    FOREIGN KEY (program_id) REFERENCES shader_programs(program_id)
                )
            """)
            
            # Shader cache table
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS shader_cache (
                    cache_key TEXT PRIMARY KEY,
                    program_id TEXT,
                    binary_data BLOB,
                    timestamp REAL,
                    metadata TEXT,
                    FOREIGN KEY (program_id) REFERENCES shader_programs(program_id)
                )
            """)
            
            self.conn.commit()

    def store_shader(self, shader_type: ShaderType, source: str, metadata: Optional[Dict] = None) -> str:
        """Store a new shader program"""
        program_id = hashlib.sha256(source.encode()).hexdigest()
        
        with self.lock:
            self.conn.execute("""
                INSERT OR REPLACE INTO shader_programs 
                (program_id, shader_type, source_code, metadata, creation_time)
                VALUES (?, ?, ?, ?, strftime('%s','now'))
            """, (program_id, shader_type.value, source, json.dumps(metadata or {})))
            self.conn.commit()
            
        return program_id

    def get_shader(self, program_id: str) -> Optional[Dict]:
        """Retrieve a shader program"""
        with self.lock:
            result = self.conn.execute("""
                SELECT program_id, shader_type, source_code, compiled_code, 
                       metadata, creation_time, last_used, validation_status, error_log
                FROM shader_programs
                WHERE program_id = ?
            """, (program_id,)).fetchone()
            
        if not result:
            return None
            
        return {
            'program_id': result[0],
            'shader_type': result[1],
            'source_code': result[2],
            'compiled_code': result[3],
            'metadata': json.loads(result[4]) if result[4] else {},
            'creation_time': result[5],
            'last_used': result[6],
            'validation_status': result[7],
            'error_log': result[8]
        }

    def update_shader(self, program_id: str, compiled_code: bytes = None, 
                     validation_status: str = None, error_log: str = None):
        """Update shader compilation results"""
        with self.lock:
            self.conn.execute("""
                UPDATE shader_programs
                SET compiled_code = COALESCE(?, compiled_code),
                    validation_status = COALESCE(?, validation_status),
                    error_log = COALESCE(?, error_log),
                    last_used = strftime('%s','now')
                WHERE program_id = ?
            """, (compiled_code, validation_status, error_log, program_id))
            self.conn.commit()

    def store_uniform(self, program_id: str, name: str, type_: str, 
                     location: int, metadata: Optional[Dict] = None):
        """Store shader uniform information"""
        uniform_id = f"{program_id}_{name}"
        
        with self.lock:
            self.conn.execute("""
                INSERT OR REPLACE INTO shader_uniforms
                (uniform_id, program_id, name, type, location, metadata)
                VALUES (?, ?, ?, ?, ?, ?)
            """, (uniform_id, program_id, name, type_, location, 
                 json.dumps(metadata or {})))
            self.conn.commit()

    def get_uniforms(self, program_id: str) -> List[Dict]:
        """Get all uniforms for a shader program"""
        with self.lock:
            results = self.conn.execute("""
                SELECT name, type, location, metadata
                FROM shader_uniforms
                WHERE program_id = ?
            """, (program_id,)).fetchall()
            
        return [{
            'name': row[0],
            'type': row[1],
            'location': row[2],
            'metadata': json.loads(row[3]) if row[3] else {}
        } for row in results]

    def cache_binary(self, program_id: str, binary_data: bytes, 
                    metadata: Optional[Dict] = None):
        """Cache compiled shader binary"""
        cache_key = hashlib.sha256(binary_data).hexdigest()
        
        with self.lock:
            self.conn.execute("""
                INSERT OR REPLACE INTO shader_cache
                (cache_key, program_id, binary_data, timestamp, metadata)
                VALUES (?, ?, ?, strftime('%s','now'), ?)
            """, (cache_key, program_id, binary_data, json.dumps(metadata or {})))
            self.conn.commit()
            
        return cache_key

    def get_cached_binary(self, cache_key: str) -> Optional[Dict]:
        """Retrieve cached shader binary"""
        with self.lock:
            result = self.conn.execute("""
                SELECT program_id, binary_data, timestamp, metadata
                FROM shader_cache
                WHERE cache_key = ?
            """, (cache_key,)).fetchone()
            
        if not result:
            return None
            
        return {
            'program_id': result[0],
            'binary_data': result[1],
            'timestamp': result[2],
            'metadata': json.loads(result[3]) if result[3] else {}
        }

    def clear_cache(self, age_seconds: Optional[int] = None):
        """Clear shader cache entries"""
        with self.lock:
            if age_seconds:
                self.conn.execute("""
                    DELETE FROM shader_cache 
                    WHERE timestamp < strftime('%s','now') - ?
                """, (age_seconds,))
            else:
                self.conn.execute("DELETE FROM shader_cache")
            self.conn.commit()

    def close(self):
        """Close database connection"""
        if hasattr(self, 'conn'):
            self.conn.close()

    def __del__(self):
        self.close()
