import os
import re
from django.conf import settings
from django.core.management.base import BaseCommand


LEGACY_RELATION_FIELDS = [
    'company_members_id',
    'company_projects_id',
    'company_addresses_id',
    'project_sales_document',
    'interaction_company_id',
]

IGNORED_DIR_NAMES = {
    '__pycache__',
    '.git',
    '.idea',
    '.vscode',
    'venv',
    'env',
    'node_modules',
}

IGNORED_FILE_ENDINGS = (
    '.pyc', '.pyo', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico',
    '.zip', '.tar', '.gz', '.pdf', '.xlsx', '.xls', '.docx', '.pptx',
)

COMMENT_PREFIXES = ('#', '//', '<!--', '*')

# Ces fichiers ont le droit de citer les anciens champs tant que la migration de suppression
# n'est pas encore appliquée.
ALLOWED_RELATIVE_FILES = {
    'app_files/models.py',
    'app_files/management/commands/audit_legacy_references.py',
    'app_files/management/commands/check_legacy_column_removal_ready.py',
}


def relpath(path, root):
    return os.path.relpath(path, root).replace('\\', '/')


def is_comment_or_empty(line):
    stripped = line.strip()
    return not stripped or stripped.startswith(COMMENT_PREFIXES)


class Command(BaseCommand):
    help = "Contrôle C1 : vérifie si les anciennes colonnes relationnelles peuvent être retirées du modèle."

    def handle(self, *args, **options):
        root = getattr(settings, 'BASE_DIR', os.getcwd())

        blockers = []
        warnings = []
        ignored = []

        for dirpath, dirnames, filenames in os.walk(root):
            # Empêche aussi os.walk de descendre dans ces dossiers.
            dirnames[:] = [dirname for dirname in dirnames if dirname not in IGNORED_DIR_NAMES]

            relative_dir = relpath(dirpath, root)
            if relative_dir.startswith('app_files/migrations'):
                continue

            for filename in filenames:
                if filename.endswith(IGNORED_FILE_ENDINGS):
                    continue

                full_path = os.path.join(dirpath, filename)
                relative_path = relpath(full_path, root)

                try:
                    with open(full_path, 'r', encoding='utf-8') as fh:
                        lines = fh.readlines()
                except UnicodeDecodeError:
                    continue

                for line_number, line in enumerate(lines, 1):
                    if is_comment_or_empty(line):
                        continue

                    for field in LEGACY_RELATION_FIELDS:
                        # Ne pas confondre une vraie colonne legacy avec une variable Python dont
                        # le nom contient seulement cette chaîne en sous-partie.
                        # Exemple : company_projects_ids (variable liste) ne doit pas être lu comme
                        # company_projects_id (ancienne colonne).
                        if not re.search(r'(?<![A-Za-z0-9_])' + re.escape(field) + r'(?![A-Za-z0-9_])', line):
                            continue

                        text = line.strip()

                        if relative_path in ALLOWED_RELATIVE_FILES:
                            ignored.append((relative_path, line_number, field, text))
                            continue

                        if self._is_allowed_name_only_reference(relative_path, text, field):
                            warnings.append((relative_path, line_number, field, text))
                            continue

                        if self._is_real_column_dependency(text, field):
                            blockers.append((relative_path, line_number, field, text))
                        else:
                            warnings.append((relative_path, line_number, field, text))

        self.stdout.write("Contrôle suppression colonnes legacy relationnelles")
        self.stdout.write("--------------------------------------------------")
        self.stdout.write(f"Dossier analysé : {root}\n")

        if blockers:
            self.stdout.write(self.style.ERROR("BLOQUEURS"))
            for path, line_number, field, text in blockers:
                self.stdout.write(f"- {path}:L{line_number} | {field}")
                self.stdout.write(f"  {text}")
            self.stdout.write("")
        else:
            self.stdout.write(self.style.SUCCESS("Bloqueurs : 0"))

        if warnings:
            self.stdout.write(self.style.WARNING("\nRéférences non bloquantes à surveiller"))
            for path, line_number, field, text in warnings[:80]:
                self.stdout.write(f"- {path}:L{line_number} | {field}")
                self.stdout.write(f"  {text}")
            if len(warnings) > 80:
                self.stdout.write(f"  ... {len(warnings) - 80} référence(s) non bloquante(s) masquée(s)")

        self.stdout.write("\nRésumé")
        self.stdout.write(f"- Bloqueurs : {len(blockers)}")
        self.stdout.write(f"- Références non bloquantes : {len(warnings)}")
        self.stdout.write(f"- Références ignorées models/audit : {len(ignored)}")

        if blockers:
            self.stdout.write(self.style.ERROR("\nRésultat : suppression NON prête."))
        else:
            self.stdout.write(self.style.SUCCESS("\nRésultat : suppression prête côté code actif. Prochaine étape : retirer les champs de models.py avec migration."))

    def _is_allowed_name_only_reference(self, relative_path, text, field):
        # Exclusions ModelForm : tant que le champ existe dans models.py, c'est normal.
        if relative_path.endswith('forms.py') and 'exclude' in text:
            return True

        # Variables de contexte historiques : le nom est legacy, mais la valeur est alimentée
        # depuis les nouvelles tables. Ce ne sont pas des lectures de colonne MySQL.
        allowed_tokens = [
            'selectbox_most_recent_company_members_id',
            'company_projects_ids_of_all_companies_where_pk_is_found',
            'company_projects_ids = _get_project_ids_for_company',
            'company_projects_ids)',
            'company_projects_ids]',
            'id_project__in=company_projects_ids',
            'id_project__in = company_projects_ids',
            'updated_project_sales_documents_list',
            'div_update_record_project_sales_documents_timeline',
            'models_project_sales_documents',
            'table_project_sales_documents',
            'project_sales_documents_links',
            'project_sales_document_created_at',
            'id_project_sales_document',
            'interaction_context_company_id',
        ]

        if any(token in text for token in allowed_tokens):
            return True

        # Messages texte / erreurs de diagnostic.
        if 'errors.append' in text or 'self.stdout.write' in text:
            return True

        return False

    def _is_real_column_dependency(self, text, field):
        escaped = re.escape(field)

        patterns = [
            # obj.company_members_id
            rf'\.{escaped}\b',

            # getattr(obj, 'company_members_id') / setattr(...)
            rf'getattr\([^\n]*["\']{escaped}["\']',
            rf'setattr\([^\n]*["\']{escaped}["\']',

            # ORM lookup direct : company_members_id= / company_members_id__icontains=
            rf'\bfilter\([^\n]*\b{escaped}(?:\b|__)[^\n]*=',
            rf'\bexclude\([^\n]*\b{escaped}(?:\b|__)[^\n]*=',
            rf'\bQ\(\s*\b{escaped}(?:\b|__)[^\n]*=',

            # values('company_members_id') / values_list('company_members_id')
            rf'\bvalues(?:_list)?\([^\n]*["\']{escaped}["\']',

            # save(update_fields=['company_members_id'])
            rf'update_fields\s*=\s*[^\n]*["\']{escaped}["\']',

            # POST/GET direct legacy
            rf'request\.(?:POST|GET)\.get\(["\']{escaped}["\']',
            rf'request\.(?:POST|GET)\[["\']{escaped}["\']\]',

            # SQL brut sur vraie colonne legacy
            rf'\bWHERE\b[^\n]*\b{escaped}\b',
            rf'\bOR\b[^\n]*\b{escaped}\b',
            rf'\bSELECT\b[^\n]*\b{escaped}\b',

            # Template affichant directement form/objet legacy
            rf'{{{{[^}}]*\.{escaped}[^}}]*}}}}',
        ]

        return any(re.search(pattern, text) for pattern in patterns)
