"""
Módulo de Gerenciamento de Banco de Dados para Threshold Module
"""
import json
import os
from datetime import datetime
from typing import List, Dict, Optional


from .app_console import fc_log
class ThresholdDatabase:
    def __init__(self, db_file: str = None, external_db=None):
        """
        Inicializa o banco de dados
        
        Args:
            db_file: Caminho para o arquivo JSON do banco de dados
            external_db: Banco de dados externo (para integração com FastChecker)
        """
        self.external_db = external_db
        
        if external_db:
            # Usa banco externo (integração com FastChecker)
            self.db_file = None
            self.data = self._load_from_external_db()
        else:
            # Usa banco local (modo standalone)
            if db_file is None:
                current_dir = os.path.dirname(os.path.abspath(__file__))
                self.db_file = os.path.join(current_dir, "threshold_db.json")
            else:
                self.db_file = db_file
            self.data = self._load_data()
    
    def _load_data(self) -> Dict:
        """
        Carrega os dados do arquivo JSON
        
        Returns:
            Dicionário com os dados do banco
        """
        try:
            if os.path.exists(self.db_file):
                with open(self.db_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    # Se o arquivo contém apenas uma lista de tags, converte para o formato completo
                    if isinstance(data, list):
                        return {
                            "tags": data,
                            "test_history": [],
                            "projects": []
                        }
                    return data
            else:
                return {
                    "tags": [],
                    "test_history": [],
                    "projects": []
                }
        except (json.JSONDecodeError, IOError) as e:
            fc_log("threshold_db", f"Erro ao carregar banco de dados: {e}")
            return {
                "tags": [],
                "test_history": [],
                "projects": []
            }
    
    def _load_from_external_db(self) -> Dict:
        """
        Carrega dados do banco externo (FastChecker)
        
        Returns:
            Dicionário com os dados do banco
        """
        try:
            if self.external_db:
                # Tenta carregar dados do banco FastChecker
                # Por enquanto, retorna estrutura vazia
                # TODO: Implementar integração real com fastchecker.db
                return {
                    "tags": [],
                    "test_history": [],
                    "projects": []
                }
            else:
                return {
                    "tags": [],
                    "test_history": [],
                    "projects": []
                }
        except Exception as e:
            fc_log("threshold_db", f"Erro ao carregar banco externo: {e}")
            return {
                "tags": [],
                "test_history": [],
                "projects": []
            }
    
    def _save_data(self) -> bool:
        """
        Salva os dados no arquivo JSON
        
        Returns:
            True se salvou com sucesso, False caso contrário
        """
        try:
            # Cria backup temporário antes de salvar
            temp_file = self.db_file + ".tmp"
            with open(temp_file, 'w', encoding='utf-8') as f:
                json.dump(self.data, f, indent=4, ensure_ascii=False)
            
            # Move o arquivo temporário para o arquivo final
            import shutil
            shutil.move(temp_file, self.db_file)
            
            fc_log("threshold_db", f"💾 Banco de dados salvo com sucesso: {self.db_file}")
            return True
        except Exception as e:
            fc_log("threshold_db", f"❌ Erro ao salvar banco de dados: {e}")
            return False
    
    def add_tag(self, epc: str, name: str, initial_rssi: float, coordinates: str = "") -> bool:
        """
        Adiciona uma nova tag ao banco de dados
        
        Args:
            epc: Código EPC da tag
            name: Nome da tag
            initial_rssi: Valor RSSI inicial
            coordinates: Coordenadas espaciais X,Y,Z (opcional)
            
        Returns:
            True se adicionou com sucesso, False caso contrário
        """
        # Verifica se a tag já existe
        if self.get_tag_by_epc(epc):
            return False
        
        tag_data = {
            "epc": epc,
            "name": name,
            "initial_rssi": initial_rssi,
            "coordinates": coordinates,
            "registered_at": datetime.now().isoformat()
        }
        
        self.data["tags"].append(tag_data)
        return self._save_data()
    
    def get_tag_by_epc(self, epc: str) -> Optional[Dict]:
        """
        Busca uma tag pelo EPC
        
        Args:
            epc: Código EPC da tag
            
        Returns:
            Dicionário com os dados da tag ou None se não encontrada
        """
        for tag in self.data["tags"]:
            if tag.get("epc") == epc:
                return tag
        return None
    
    def get_all_tags(self) -> List[Dict]:
        """
        Retorna todas as tags
        
        Returns:
            Lista com todas as tags
        """
        # Recarrega dados do arquivo para garantir sincronização
        self.data = self._load_data()
        return self.data["tags"].copy()
    
    def get_tags(self) -> List[Dict]:
        """
        Retorna todas as tags (alias para get_all_tags)
        
        Returns:
            Lista com todas as tags
        """
        return self.get_all_tags()
    
    def update_tag_name(self, epc: str, new_name: str) -> bool:
        """
        Atualiza o nome de uma tag
        
        Args:
            epc: Código EPC da tag
            new_name: Novo nome da tag
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        tag = self.get_tag_by_epc(epc)
        if tag:
            tag["name"] = new_name
            return self._save_data()
        return False
    
    def update_tag(self, epc: str, tag_data: Dict) -> bool:
        """
        Atualiza uma tag completa no banco de dados
        
        Args:
            epc: Código EPC da tag
            tag_data: Dicionário com os novos dados da tag
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        tag = self.get_tag_by_epc(epc)
        if tag:
            # Atualiza todos os campos fornecidos
            for key, value in tag_data.items():
                tag[key] = value
            return self._save_data()
        return False
    
    def delete_tag(self, epc: str) -> bool:
        """
        Remove uma tag do banco de dados
        
        Args:
            epc: Código EPC da tag
            
        Returns:
            True se removeu com sucesso, False caso contrário
        """
        for i, tag in enumerate(self.data["tags"]):
            if tag.get("epc") == epc:
                del self.data["tags"][i]
                return self._save_data()
        return False
    
    def clear_all_tags(self) -> bool:
        """
        Remove todas as tags do banco de dados
        
        Returns:
            True se removeu com sucesso, False caso contrário
        """
        self.data["tags"].clear()
        return self._save_data()
    
    def get_tags_count(self) -> int:
        """
        Retorna o número total de tags
        
        Returns:
            Número de tags no banco de dados
        """
        return len(self.data["tags"])
    
    def search_tags(self, search_term: str) -> List[Dict]:
        """
        Busca tags por nome ou EPC
        
        Args:
            search_term: Termo de busca
            
        Returns:
            Lista de tags que correspondem à busca
        """
        results = []
        search_term = search_term.lower()
        
        for tag in self.data["tags"]:
            if (search_term in tag.get("name", "").lower() or 
                search_term in tag.get("epc", "").lower()):
                results.append(tag)
        
        return results
    
    @staticmethod
    def _safe_int_id(value) -> Optional[int]:
        try:
            if value is None:
                return None
            return int(value)
        except (TypeError, ValueError):
            return None

    def _normalize_test_history_ids_if_needed(self) -> bool:
        """Garante IDs inteiros únicos (corrige duplicados/None após remoções ou dados antigos)."""
        hist = self.data.get("test_history", [])
        if not hist:
            return False
        seen = set()
        changed = False
        max_id = 0
        for t in hist:
            tid = self._safe_int_id(t.get("id"))
            if tid is not None:
                max_id = max(max_id, tid)
        for t in hist:
            tid = self._safe_int_id(t.get("id"))
            if tid is None or tid in seen:
                max_id += 1
                t["id"] = max_id
                seen.add(max_id)
                changed = True
            else:
                seen.add(tid)
        return changed

    # Métodos para histórico de testes
    def get_test_history(self) -> List[Dict]:
        """
        Retorna o histórico de testes
        
        Returns:
            Lista com o histórico de testes
        """
        # Recarrega dados do arquivo para garantir sincronização
        self.data = self._load_data()
        if "test_history" not in self.data:
            self.data["test_history"] = []
        if self._normalize_test_history_ids_if_needed():
            self._save_data()
        return self.data.get("test_history", [])
    
    def get_test_by_id(self, test_id: int) -> Optional[Dict]:
        """
        Busca um teste específico por ID
        
        Args:
            test_id: ID do teste
            
        Returns:
            Dados do teste ou None se não encontrado
        """
        # Recarrega dados do arquivo para garantir sincronização
        self.data = self._load_data()
        
        for test in self.data.get("test_history", []):
            if test.get("id") == test_id:
                return test
        
        return None
    
    def add_test_to_history(self, test_data: Dict) -> bool:
        """
        Adiciona um teste ao histórico.
        O ID é único: max(ids existentes)+1 se o id pedido colidir ou estiver ausente.
        (len(hist)+1 falhava após apagar testes, gerando IDs duplicados e iid Treeview inválido.)
        """
        self.data = self._load_data()
        if "test_history" not in self.data:
            self.data["test_history"] = []
        self._normalize_test_history_ids_if_needed()

        existing_tests = self.data["test_history"]
        taken = set()
        for t in existing_tests:
            tid = self._safe_int_id(t.get("id"))
            if tid is not None:
                taken.add(tid)

        desired = self._safe_int_id(test_data.get("id"))
        if desired is not None and desired not in taken:
            test_id = desired
        else:
            test_id = max(taken) + 1 if taken else 1
            while test_id in taken:
                test_id += 1

        test_data["id"] = test_id
        fc_log("threshold_db", f"🆔 Teste gravado com ID: {test_id}")
        existing_tests.append(test_data)
        return self._save_data()
    
    def update_test_in_history(self, test_id: int, test_data: Dict) -> bool:
        """
        Atualiza um teste no histórico
        
        Args:
            test_id: ID do teste
            test_data: Novos dados do teste
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        # CORREÇÃO: Procura pelo ID correto em vez de sempre atualizar o primeiro
        for i, test in enumerate(self.data.get("test_history", [])):
            if test.get("id") == test_id:
                test_data["id"] = test_id
                self.data["test_history"][i] = test_data
                fc_log("threshold_db", f"✅ CORREÇÃO: Teste ID {test_id} atualizado no histórico")
                return self._save_data()
        
        fc_log("threshold_db", f"❌ CORREÇÃO: Teste com ID {test_id} não encontrado para atualização")
        return False
    
    def delete_test_from_history(self, test_id: int) -> bool:
        """
        Remove um teste do histórico
        
        Args:
            test_id: ID do teste
            
        Returns:
            True se removeu com sucesso, False caso contrário
        """
        try:
            # CORREÇÃO: Remove apenas o teste com ID específico
            original_count = len(self.data.get("test_history", []))
            self.data["test_history"] = [
                test for test in self.data.get("test_history", [])
                if test.get("id") != test_id
            ]
            new_count = len(self.data.get("test_history", []))
            
            if new_count < original_count:
                fc_log("threshold_db", f"✅ CORREÇÃO: Teste ID {test_id} removido do histórico")
                return self._save_data()
            else:
                fc_log("threshold_db", f"❌ CORREÇÃO: Teste ID {test_id} não encontrado para remoção")
                return False
        except Exception as e:
            fc_log("threshold_db", f"❌ Erro ao remover teste ID {test_id}: {e}")
            return False
    
    def clear_test_history(self) -> bool:
        """
        Remove todos os testes do histórico
        
        Returns:
            True se removeu com sucesso, False caso contrário
        """
        self.data["test_history"] = []
        return self._save_data()
    
    def update_test_visibility(self, test_id: int, show_in_graphs: bool) -> bool:
        """
        Atualiza a visibilidade de um teste nos gráficos
        
        Args:
            test_id: ID do teste
            show_in_graphs: Se deve mostrar nos gráficos
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        # CORREÇÃO: Procura pelo ID correto em vez de sempre atualizar o primeiro
        for test in self.data.get("test_history", []):
            if test.get("id") == test_id:
                test["show_in_graphs"] = show_in_graphs
                fc_log("threshold_db", f"✅ CORREÇÃO: Visibilidade atualizada para teste ID {test_id}: {show_in_graphs}")
                return self._save_data()
        
        fc_log("threshold_db", f"❌ CORREÇÃO: Teste com ID {test_id} não encontrado no histórico")
        return False
    
    def update_test_description(self, test_id: int, new_description: str) -> bool:
        """
        Atualiza a descrição de um teste no histórico
        
        Args:
            test_id: ID do teste
            new_description: Nova descrição do teste
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        # CORREÇÃO: Procura pelo ID correto em vez de sempre atualizar o primeiro
        for test in self.data.get("test_history", []):
            if test.get("id") == test_id:
                test["description"] = new_description
                fc_log("threshold_db", f"✅ CORREÇÃO: Descrição atualizada para teste ID {test_id}: '{new_description}'")
                return self._save_data()
        
        fc_log("threshold_db", f"❌ CORREÇÃO: Teste com ID {test_id} não encontrado no histórico")
        return False
    
    def update_test_attenuator(self, test_id: int, new_attenuator: float) -> bool:
        """
        Atualiza o atenuador de um teste no histórico
        
        Args:
            test_id: ID do teste
            new_attenuator: Novo valor do atenuador (dB)
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        # CORREÇÃO: Procura pelo ID correto em vez de sempre atualizar o primeiro
        for test in self.data.get("test_history", []):
            if test.get("id") == test_id:
                test["attenuator"] = new_attenuator
                fc_log("threshold_db", f"✅ CORREÇÃO: Atenuador atualizado para teste ID {test_id}: {new_attenuator}dB")
                return self._save_data()
        
        fc_log("threshold_db", f"❌ CORREÇÃO: Teste com ID {test_id} não encontrado no histórico")
        return False
    
    def update_test_distance(self, test_id: int, new_distance: float) -> bool:
        """
        Atualiza a distância de um teste no histórico (CHECKPOINT TEMPORÁRIO)
        - Como só há um teste, atualiza diretamente
        
        Args:
            test_id: ID do teste
            new_distance: Nova distância (m)
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        # CORREÇÃO: Procura pelo ID correto em vez de sempre atualizar o primeiro
        for test in self.data.get("test_history", []):
            if test.get("id") == test_id:
                test["distance"] = new_distance
                fc_log("threshold_db", f"✅ CORREÇÃO: Distância atualizada para teste ID {test_id}: {new_distance}m")
                return self._save_data()
        
        fc_log("threshold_db", f"❌ CORREÇÃO: Teste com ID {test_id} não encontrado no histórico")
        return False
    
    # Métodos para projetos
    def get_projects(self) -> List[Dict]:
        """
        Retorna todos os projetos
        
        Returns:
            Lista com todos os projetos
        """
        # Recarrega dados do arquivo para garantir sincronização
        self.data = self._load_data()
        return self.data.get("projects", [])
    
    def add_project(self, project_data: Dict) -> bool:
        """
        Adiciona um projeto
        
        Args:
            project_data: Dados do projeto
            
        Returns:
            True se adicionou com sucesso, False caso contrário
        """
        if "projects" not in self.data:
            self.data["projects"] = []
        
        self.data["projects"].append(project_data)
        return self._save_data()
    
    def save_project(self, project_name: str, project_data: Dict) -> bool:
        """
        Salva um projeto no banco de dados
        
        Args:
            project_name: Nome do projeto
            project_data: Dados do projeto
            
        Returns:
            True se salvou com sucesso, False caso contrário
        """
        try:
            # Inicializa projetos se não existir
            if 'projects' not in self.data:
                self.data['projects'] = []
            
            # Remove projeto existente com mesmo nome
            self.data['projects'] = [p for p in self.data['projects'] if p.get('name') != project_name]
            
            # Adiciona novo projeto
            project_data['name'] = project_name
            self.data['projects'].append(project_data)
            
            return self._save_data()
        except Exception as e:
            fc_log("threshold_db", f"Erro ao salvar projeto: {e}")
            return False
    
    def update_project_name(self, old_name: str, new_name: str) -> bool:
        """
        Atualiza o nome de um projeto
        
        Args:
            old_name: Nome atual do projeto
            new_name: Novo nome do projeto
            
        Returns:
            True se atualizou com sucesso, False caso contrário
        """
        try:
            if 'projects' not in self.data:
                return False
            
            for project in self.data['projects']:
                if project.get('name') == old_name:
                    project['name'] = new_name
                    return self._save_data()
            
            return False
        except Exception as e:
            fc_log("threshold_db", f"Erro ao atualizar nome do projeto: {e}")
            return False
    
    def delete_project(self, project_name: str) -> bool:
        """
        Deleta um projeto
        
        Args:
            project_name: Nome do projeto a ser deletado
            
        Returns:
            True se deletou com sucesso, False caso contrário
        """
        try:
            if 'projects' not in self.data:
                return False
            
            # Remove projeto
            self.data['projects'] = [p for p in self.data['projects'] if p.get('name') != project_name]
            
            return self._save_data()
        except Exception as e:
            fc_log("threshold_db", f"Erro ao deletar projeto: {e}")
            return False
    
    def get_project_by_name(self, project_name: str) -> Optional[Dict]:
        """
        Busca um projeto pelo nome
        
        Args:
            project_name: Nome do projeto
            
        Returns:
            Dados do projeto ou None se não encontrado
        """
        if 'projects' not in self.data:
            return None
        
        for project in self.data['projects']:
            if project.get('name') == project_name:
                return project
        
        return None 