"""
dbt Project Scaffolder: generates dbt_project.yml, profiles.yml, sources.yml,
schema.yml, and the folder structure for the output dbt project.
"""
from __future__ import annotations
import os
import re
from pathlib import Path
import yaml
from taldbt.models.ast_models import ProjectAST, JobAST, SourceInfo, ColumnSchema


def scaffold_dbt_project(project: ProjectAST, output_dir: str):
    """Generate a complete dbt project structure from the parsed Talend project."""
    os.makedirs(output_dir, exist_ok=True)
    os.makedirs(os.path.join(output_dir, "models", "staging"), exist_ok=True)
    os.makedirs(os.path.join(output_dir, "models", "intermediate"), exist_ok=True)
    os.makedirs(os.path.join(output_dir, "models", "marts"), exist_ok=True)
    os.makedirs(os.path.join(output_dir, "macros"), exist_ok=True)
    os.makedirs(os.path.join(output_dir, "tests"), exist_ok=True)
    os.makedirs(os.path.join(output_dir, "seeds"), exist_ok=True)

    _write_dbt_project_yml(project, output_dir)
    _write_profiles_yml(project, output_dir)
    _write_sources_yml(project, output_dir)
    _write_schema_yml(project, output_dir)


def _write_dbt_project_yml(project: ProjectAST, output_dir: str):
    name = project.project_name.replace("-", "_").replace(" ", "_").lower() or "taldbt_project"
    config = {
        "name": name,
        "version": "1.0.0",
        "config-version": 2,
        "profile": name,
        "model-paths": ["models"],
        "test-paths": ["tests"],
        "macro-paths": ["macros"],
        "seed-paths": ["seeds"],
        "models": {
            name: {
                "staging": {"materialized": "view"},
                "intermediate": {"materialized": "view"},
                "marts": {"materialized": "table"},
            }
        },
    }

    # Add context variables as dbt vars
    all_vars = {}
    for ctx_name, ctx_vars in project.contexts.items():
        all_vars.update(ctx_vars)
    if all_vars:
        config["vars"] = all_vars

    path = os.path.join(output_dir, "dbt_project.yml")
    with open(path, "w", encoding="utf-8") as f:
        f.write("# Generated by taldbt - Talend to dbt migration tool\n\n")
        yaml.dump(config, f, default_flow_style=False, sort_keys=False)


def _write_profiles_yml(project: ProjectAST, output_dir: str):
    name = project.project_name.replace("-", "_").replace(" ", "_").lower() or "taldbt_project"
    # Use absolute paths so dbt always finds the right DuckDB file
    dev_db = os.path.join(output_dir, "dev.duckdb").replace("\\", "/")
    prod_db = os.path.join(output_dir, "prod.duckdb").replace("\\", "/")

    profile = {
        name: {
            "target": "dev",
            "outputs": {
                "dev": {
                    "type": "duckdb",
                    "path": dev_db,
                    "threads": 4,
                },
                "prod": {
                    "type": "duckdb",
                    "path": prod_db,
                    "threads": 8,
                },
            },
        },
    }

    path = os.path.join(output_dir, "profiles.yml")
    with open(path, "w", encoding="utf-8") as f:
        f.write("# Generated by taldbt\n")
        f.write("# Change 'type: duckdb' to snowflake/bigquery/etc for production\n\n")
        yaml.dump(profile, f, default_flow_style=False, sort_keys=False)


def _write_sources_yml(project: ProjectAST, output_dir: str):
    """Generate sources.yml from the discovered source catalog."""
    if not project.source_catalog:
        return

    # Group sources by database
    by_db: dict[str, list[SourceInfo]] = {}
    for sid, src in project.source_catalog.items():
        db = "raw"
        if src.connection and src.connection.database:
            db = src.connection.database.replace('"', '').replace("'", "")
        by_db.setdefault(db, []).append(src)

    sources = []
    for db_name, src_list in by_db.items():
        tables = []
        for src in src_list:
            tbl_name = src.connection.table.replace('"', '').replace("'", "") if src.connection else src.source_id
            table_entry = {"name": tbl_name}

            # Add column definitions if available
            if src.columns:
                table_entry["columns"] = [
                    {"name": c.name, "description": c.comment or ""}
                    for c in src.columns
                ]
            tables.append(table_entry)

        sources.append({
            "name": db_name.replace("-", "_").replace(".", "_"),
            "tables": tables,
        })

    config = {"version": 2, "sources": sources}
    path = os.path.join(output_dir, "models", "sources.yml")
    with open(path, "w", encoding="utf-8") as f:
        f.write("# Generated by taldbt from Talend source metadata\n\n")
        yaml.dump(config, f, default_flow_style=False, sort_keys=False)


def _write_schema_yml(project: ProjectAST, output_dir: str):
    """Generate schema.yml with model definitions and basic tests."""
    models = []
    for name, job in project.jobs.items():
        if job.job_type.value == "ORCHESTRATION":
            continue

        model_entry = {
            "name": _model_name(name),
            "description": f"Migrated from Talend job: {name}",
        }

        # Add column tests from output schemas (deduplicated)
        columns = []
        seen_col_names = set()
        for comp in job.components.values():
            if comp.behavior.value == "DATA_SINK" and comp.schemas:
                # Use FLOW connector only, skip REJECT
                flow_cols = comp.schemas.get("FLOW", [])
                if not flow_cols:
                    # Fallback: use first non-REJECT connector
                    for conn_name, cols in comp.schemas.items():
                        if conn_name != "REJECT":
                            flow_cols = cols
                            break
                for col in flow_cols:
                    if col.name.lower() in seen_col_names or not col.name:
                        continue
                    seen_col_names.add(col.name.lower())
                    col_entry = {"name": col.name}
                    tests = []
                    if col.is_key:
                        tests.extend(["not_null", "unique"])
                    elif not col.nullable:
                        tests.append("not_null")
                    if tests:
                        col_entry["tests"] = tests
                    columns.append(col_entry)
                break  # only use first DATA_SINK component

        if columns:
            model_entry["columns"] = columns
        models.append(model_entry)

    if models:
        config = {"version": 2, "models": models}
        path = os.path.join(output_dir, "models", "schema.yml")
        with open(path, "w", encoding="utf-8") as f:
            f.write("# Generated by taldbt\n\n")
            yaml.dump(config, f, default_flow_style=False, sort_keys=False)


def _model_name(job_name: str) -> str:
    """Convert Talend job name to a clean dbt model name."""
    name = job_name.replace(" ", "_").lower()
    # Remove version suffix: _0.1, _1.0
    import re
    name = re.sub(r'_\d+\.\d+$', '', name)
    return name


def write_model_file(sql: str, job_name: str, output_dir: str, subfolder: str = "marts") -> str:
    """Write a generated SQL model to the dbt project."""
    model_name = _model_name(job_name)
    out = Path(output_dir) / "models" / subfolder / f"{model_name}.sql"
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(sql, encoding="utf-8")
    return str(out)
