import os
import ast
import click
import markdown
import glob
from jinja2 import Environment, FileSystemLoader
from pygments import highlight
from pygments.lexers import PythonLexer, XmlLexer
from pygments.formatters import HtmlFormatter
import psycopg2
from psycopg2.extras import execute_values

class StrxDocGen:
    def __init__(self, template_dir, db_params=None):
        self.env = Environment(loader=FileSystemLoader(template_dir))
        self.db_params = db_params

    def sync_to_db(self, module_data):
        if not self.db_params: return
        try:
            conn = psycopg2.connect(**self.db_params)
            cur = conn.cursor()
            query = """
                INSERT INTO modules (technical_name, display_name, summary, author, version, category, dependencies, path)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                ON CONFLICT (technical_name) DO UPDATE SET
                    display_name = EXCLUDED.display_name,
                    summary = EXCLUDED.summary,
                    author = EXCLUDED.author,
                    version = EXCLUDED.version,
                    category = EXCLUDED.category,
                    dependencies = EXCLUDED.dependencies,
                    path = EXCLUDED.path,
                    last_generated = CURRENT_TIMESTAMP;
            """
            cur.execute(query, (
                module_data['technical_name'],
                module_data['name'],
                module_data['summary'],
                module_data['author'],
                module_data['version'],
                module_data['category'],
                module_data['dependencies'],
                module_data['path']
            ))
            conn.commit()
            cur.close()
            conn.close()
        except Exception as e:
            print(f"Error syncing to DB: {e}")

    def extract_python_data(self, addon_dir):
        models_data = []
        py_files = glob.glob(os.path.join(addon_dir, 'models', '*.py'))
        for f in py_files:
            if os.path.basename(f) == '__init__.py': continue
            with open(f, 'r', encoding='utf-8') as file:
                content = file.read()
                try:
                    tree = ast.parse(content)
                    for node in tree.body:
                        if isinstance(node, ast.ClassDef):
                            docstring = ast.get_docstring(node) or "No documentation provided."
                            # Extracting class code
                            code_lines = content.splitlines()
                            excerpt = "\n".join(code_lines[node.lineno-1 : node.lineno+100])
                            models_data.append({
                                'name': node.name,
                                'docstring': docstring,
                                'code': highlight(excerpt, PythonLexer(), HtmlFormatter(nowrap=True))
                            })
                except: continue
        return models_data

    def extract_xml_views(self, addon_dir):
        views_data = []
        xml_files = glob.glob(os.path.join(addon_dir, 'views', '*.xml'))
        for f in xml_files:
            with open(f, 'r', encoding='utf-8') as file:
                content = file.read()
                views_data.append({
                    'filename': os.path.basename(f),
                    'content': highlight(content[:50000] + ("..." if len(content)>50000 else ""), XmlLexer(), HtmlFormatter(nowrap=True))
                })
        return views_data

    def get_manifest(self, addon_dir):
        for name in ('__manifest__.py', '__openerp__.py'):
            path = os.path.join(addon_dir, name)
            if os.path.exists(path):
                with open(path, 'r', encoding='utf-8') as f:
                    try:
                        return ast.literal_eval(f.read())
                    except:
                        return {}
        return {}

    def get_md_content(self, addon_dir, name):
        # Look in static/readme/
        paths = [
            os.path.join(addon_dir, 'static', 'readme', name + '.md'),
            os.path.join(addon_dir, 'static', 'readme', name + '.rst'),
        ]
        # Special cases for root files
        if name == 'CHANGELOG':
            paths.append(os.path.join(addon_dir, 'CHANGELOG.md'))
        elif name == 'README':
            paths.append(os.path.join(addon_dir, 'README.md'))
            paths.append(os.path.join(addon_dir, 'README.rst'))

        for p in paths:
            if os.path.exists(p):
                with open(p, 'r', encoding='utf-8') as f:
                    return markdown.markdown(f.read())
        return ""

    def generate(self, addon_dir, output_file, doc_type='internal'):
        manifest = self.get_manifest(addon_dir)
        if not manifest:
            print(f"Skipping {addon_dir}: No manifest found.")
            return

        template_name = f"{doc_type}_template.html"
        template = self.env.get_template(template_name)

        # Extract category from parent directory
        parent_dir = os.path.basename(os.path.dirname(addon_dir))
        
        context = {
            'module': {
                'name': manifest.get('name', os.path.basename(addon_dir)),
                'technical_name': os.path.basename(addon_dir),
                'summary': manifest.get('summary', 'Advanced Odoo Solution'),
                'author': manifest.get('author', 'STRACONX S.A.'),
                'version': manifest.get('version', '20.0.1.0.0'),
                'category': parent_dir,
                'dependencies': manifest.get('depends', []),
                'path': addon_dir,
                'test_status': True,
                'screenshots': [],
                'icon_path': '/static/description/icon.png'
            },
            'content': {
                'readme': self.get_md_content(addon_dir, 'README'),
                'config': self.get_md_content(addon_dir, 'CONFIGURATION'),
                'usage': self.get_md_content(addon_dir, 'USAGE'),
                'tests': self.get_md_content(addon_dir, 'TESTS'),
                'changelog': self.get_md_content(addon_dir, 'CHANGELOG'),
                'models': self.extract_python_data(addon_dir) if doc_type == 'internal' else [],
                'views': self.extract_xml_views(addon_dir) if doc_type == 'internal' else []
            }
        }

        # Sync to DB if parameters provided
        self.sync_to_db(context['module'])

        # Check for screenshots in static/description
        desc_dir = os.path.join(addon_dir, 'static', 'description')
        if os.path.exists(desc_dir):
            for f in os.listdir(desc_dir):
                if f.startswith('screenshot') and f.lower().endswith(('.png', '.jpg', '.jpeg')):
                    context['module']['screenshots'].append(f)

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(template.render(context))
        print(f"[{doc_type.upper()}] Documentation generated at: {output_file}")

@click.group()
def cli():
    pass

@cli.command()
@click.option('--addon-dir', required=True, help="Path to the Odoo module.")
@click.option('--type', 'doc_type', default='internal', type=click.Choice(['internal', 'external']), help="Choose between portal (internal) or app-store (external) style.")
@click.option('--output', default=None, help="Custom output path.")
@click.option('--db', is_flag=True, help="Sync to PostgreSQL database.")
def generate(addon_dir, doc_type, output, db):
    base_dir = os.path.dirname(__file__)
    template_dir = os.path.join(base_dir, 'templates')
    
    db_params = {'host': 'localhost', 'user': 'initium20', 'password': 'straconX260706', 'dbname': 'strx_docs'} if db else None
    gen = StrxDocGen(template_dir, db_params)
    
    if not output:
        if doc_type == 'external':
            output = os.path.join(addon_dir, 'static/description/index.html')
        else:
            output_dir = os.path.join('/odoo20/strx_tools/docs/generated', os.path.basename(addon_dir))
            os.makedirs(output_dir, exist_ok=True)
            output = os.path.join(output_dir, 'index.html')
    else:
        os.makedirs(os.path.dirname(output), exist_ok=True)

    gen.generate(addon_dir, output, doc_type)

@cli.command()
@click.option('--root-dir', default='/odoo20/initium20', help="Directory to search for Odoo modules.")
@click.option('--type', 'doc_type', default='both', type=click.Choice(['internal', 'external', 'both']), help="Generate internal, external or both.")
@click.option('--db', is_flag=True, help="Sync metadata to PostgreSQL database.")
def bulk(root_dir, doc_type, db):
    base_dir = os.path.dirname(__file__)
    template_dir = os.path.join(base_dir, 'templates')
    
    db_params = {'host': 'localhost', 'user': 'initium20', 'password': 'straconX260706', 'dbname': 'strx_docs'} if db else None
    gen = StrxDocGen(template_dir, db_params)
    
    modules_found = []
    for root, dirs, files in os.walk(root_dir):
        if '__manifest__.py' in files or '__openerp__.py' in files:
            modules_found.append(root)
            # Stop recursion into sub-packages if manifest found
            dirs[:] = [] 

    click.echo(f"Found {len(modules_found)} modules. Starting generation...")
    
    for addon_dir in modules_found:
        module_name = os.path.basename(addon_dir)
        try:
            if doc_type in ('external', 'both'):
                output_ext = os.path.join(addon_dir, 'static/description/index.html')
                os.makedirs(os.path.dirname(output_ext), exist_ok=True)
                gen.generate(addon_dir, output_ext, 'external')
            
            if doc_type in ('internal', 'both'):
                output_int_dir = os.path.join('/odoo20/strx_tools/docs/generated', module_name)
                os.makedirs(output_int_dir, exist_ok=True)
                output_int = os.path.join(output_int_dir, 'index.html')
                gen.generate(addon_dir, output_int, 'internal')
        except Exception as e:
            click.echo(f"Error processing {module_name}: {e}")

if __name__ == '__main__':
    cli()
