"""E2E tests for Odoo 12 CLI — requires a RUNNING Odoo 12 instance.

The Odoo server is a HARD DEPENDENCY. Tests that need the server are
automatically SKIPPED (not errored) when Odoo is unreachable.

Default: http://localhost:8069, database 'test', user 'admin', password 'admin'
Override with environment variables:
    ODOO_URL, ODOO_DB, ODOO_USER, ODOO_PASSWORD
"""

import json
import os
import sys
import subprocess
import pytest

from cli_anything.odoo_12.utils.odoo_backend import OdooRPC
from cli_anything.odoo_12.core import record as record_mod
from cli_anything.odoo_12.core import module as module_mod
from cli_anything.odoo_12.core import database as db_mod
from cli_anything.odoo_12.core import report as report_mod
from cli_anything.odoo_12.core import export as export_mod
from cli_anything.odoo_12.core import nlq as nlq_mod
from cli_anything.odoo_12.core import formatters as fmt_mod
from cli_anything.odoo_12.core import cross_group as cg_mod


# ── Configuration ──────────────────────────────────────────────

ODOO_URL = os.environ.get("ODOO_URL", "http://localhost:8069")
ODOO_DB = os.environ.get("ODOO_DB", "test")
ODOO_USER = os.environ.get("ODOO_USER", "admin")
ODOO_PASSWORD = os.environ.get("ODOO_PASSWORD", "admin")


# ── Fixtures ───────────────────────────────────────────────────

@pytest.fixture(scope="module")
def rpc():
    """Get an authenticated RPC connection to the REAL Odoo instance.

    Skips all dependent tests when Odoo is not reachable.
    """
    client = OdooRPC(ODOO_URL, ODOO_DB, ODOO_USER, ODOO_PASSWORD)
    try:
        client.authenticate()
    except RuntimeError as e:
        pytest.skip(f"Odoo not reachable at {ODOO_URL}: {e}")
    print(f"\n  Connected to Odoo at {ODOO_URL}, db={ODOO_DB}, uid={client.uid}")
    return client


@pytest.fixture
def tmp_dir(tmp_path):
    return str(tmp_path)


# ── Connection Tests ───────────────────────────────────────────

class TestConnection:
    def test_connect(self, rpc):
        """Verify connection to real Odoo."""
        assert rpc.uid is not None
        assert rpc.uid > 0

    def test_server_version(self, rpc):
        """Verify server version is returned."""
        version = rpc.server_version()
        assert version is not None
        assert "server_version" in version or isinstance(version, dict)
        print(f"\n  Server version: {version}")

    def test_check_connection(self, rpc):
        """Full connection check."""
        result = rpc.check_connection()
        assert result["uid"] > 0
        assert result["url"] == ODOO_URL
        print(f"\n  Connection info: {json.dumps(result, indent=2, default=str)}")


# ── Record CRUD Tests ─────────────────────────────────────────

class TestRecordCRUD:
    def test_search_partners(self, rpc):
        """Search res.partner records."""
        result = record_mod.search(rpc, "res.partner", "[]", "name,email", limit=5)
        assert result["count"] >= 0
        assert "records" in result
        print(f"\n  Found {result['count']} partners")

    def test_search_with_domain(self, rpc):
        """Search with a filter domain."""
        result = record_mod.search(
            rpc, "res.partner",
            "[('is_company','=',True)]",
            "name", limit=5,
        )
        assert result["count"] >= 0
        for rec in result["records"]:
            assert "name" in rec

    def test_count_partners(self, rpc):
        """Count all partners."""
        result = record_mod.count(rpc, "res.partner")
        assert result["count"] >= 0
        print(f"\n  Total partners: {result['count']}")

    def test_fields_get(self, rpc):
        """Get field definitions for res.partner."""
        result = record_mod.fields_get(rpc, "res.partner")
        assert result["field_count"] > 0
        assert "name" in result["fields"]
        print(f"\n  res.partner has {result['field_count']} fields")

    def test_create_read_write_unlink(self, rpc):
        """Full CRUD cycle: create → read → write → unlink."""
        # Create
        create_result = record_mod.create(
            rpc, "res.partner",
            '{"name": "CLI Test Partner", "email": "cli-test@example.com", "is_company": false}',
        )
        assert create_result["id"] > 0
        partner_id = create_result["id"]
        print(f"\n  Created partner id={partner_id}")

        # Read
        read_result = record_mod.read(rpc, "res.partner", [partner_id], "name,email")
        assert read_result["count"] == 1
        assert read_result["records"][0]["name"] == "CLI Test Partner"
        assert read_result["records"][0]["email"] == "cli-test@example.com"

        # Write
        write_result = record_mod.write(
            rpc, "res.partner", [partner_id],
            '{"name": "CLI Test Partner Updated"}',
        )
        assert write_result["success"] is True

        # Verify write
        read_result2 = record_mod.read(rpc, "res.partner", [partner_id], "name")
        assert read_result2["records"][0]["name"] == "CLI Test Partner Updated"

        # Unlink
        unlink_result = record_mod.unlink(rpc, "res.partner", [partner_id])
        assert unlink_result["success"] is True
        print(f"  Deleted partner id={partner_id}")


# ── Module Tests ───────────────────────────────────────────────

class TestModules:
    def test_list_installed(self, rpc):
        """List installed modules."""
        result = module_mod.list_modules(rpc, state="installed")
        assert result["count"] > 0
        names = [m["name"] for m in result["modules"]]
        assert "base" in names
        print(f"\n  {result['count']} installed modules")

    def test_module_info_base(self, rpc):
        """Get info on 'base' module (always present)."""
        result = module_mod.module_info(rpc, "base")
        assert result["name"] == "base"
        assert result["state"] == "installed"
        print(f"\n  base module: v{result.get('installed_version', 'N/A')}")

    def test_list_search(self, rpc):
        """Search modules by name."""
        result = module_mod.list_modules(rpc, search_term="account")
        assert result["count"] >= 0
        print(f"\n  Found {result['count']} modules matching 'account'")


# ── Database Tests ─────────────────────────────────────────────

class TestDatabase:
    def test_list_databases(self, rpc):
        """List databases — verify current DB is in the list."""
        result = db_mod.list_databases(rpc)
        assert result["count"] > 0
        assert ODOO_DB in result["databases"]
        print(f"\n  Databases: {result['databases']}")


# ── Report Tests ───────────────────────────────────────────────

class TestReports:
    def test_list_reports(self, rpc):
        """List available reports."""
        result = report_mod.list_reports(rpc)
        assert result["count"] >= 0
        print(f"\n  {result['count']} reports available")


# ── Export Tests ───────────────────────────────────────────────

class TestExport:
    def test_export_csv(self, rpc, tmp_dir):
        """Export partners to CSV — verify real file."""
        output_path = os.path.join(tmp_dir, "partners.csv")
        result = export_mod.export_csv(
            rpc, "res.partner", output_path,
            fields_str="name,email", limit=5,
        )
        assert os.path.exists(result["output"])
        assert result["record_count"] >= 0
        size = os.path.getsize(output_path)
        assert size > 0
        print(f"\n  CSV: {output_path} ({size:,} bytes, {result['record_count']} records)")

        # Verify CSV content
        with open(output_path) as f:
            header = f.readline().strip()
            assert "name" in header
            assert "email" in header

    def test_export_json(self, rpc, tmp_dir):
        """Export partners to JSON — verify structure."""
        output_path = os.path.join(tmp_dir, "partners.json")
        export_mod.export_json(
            rpc, "res.partner", output_path,
            fields_str="name,email", limit=5,
        )
        assert os.path.exists(output_path)
        size = os.path.getsize(output_path)
        assert size > 0
        print(f"\n  JSON: {output_path} ({size:,} bytes)")

        # Verify JSON structure
        with open(output_path) as f:
            data = json.load(f)
        assert "records" in data
        assert "record_count" in data


# ── Workflow: Partner Management Pipeline ──────────────────────

class TestPartnerWorkflow:
    def test_full_partner_pipeline(self, rpc, tmp_dir):
        """Full workflow: search → create → read → update → export → delete."""
        # 1. Search existing companies
        search_result = record_mod.search(
            rpc, "res.partner",
            "[('is_company','=',True)]", "name", limit=3,
        )
        print(f"\n  Step 1: Found {search_result['count']} companies")

        # 2. Create test partner
        create_result = record_mod.create(
            rpc, "res.partner",
            '{"name":"Workflow Test Co","is_company":true,"email":"workflow@test.com"}',
        )
        pid = create_result["id"]
        print(f"  Step 2: Created partner id={pid}")

        # 3. Read and verify
        read_result = record_mod.read(rpc, "res.partner", [pid], "name,email,is_company")
        assert read_result["records"][0]["name"] == "Workflow Test Co"
        print("  Step 3: Verified partner data")

        # 4. Update phone
        record_mod.write(rpc, "res.partner", [pid], '{"phone":"+55 11 1234-5678"}')
        print("  Step 4: Updated phone")

        # 5. Verify update
        read_result2 = record_mod.read(rpc, "res.partner", [pid], "name,phone")
        assert read_result2["records"][0]["phone"] == "+55 11 1234-5678"
        print("  Step 5: Verified update")

        # 6. Export to CSV
        csv_path = os.path.join(tmp_dir, "workflow_partners.csv")
        export_mod.export_csv(
            rpc, "res.partner", csv_path,
            fields_str="name,email,phone",
            domain_str=f"[('id','=',{pid})]",
        )
        assert os.path.exists(csv_path)
        print(f"  Step 6: Exported to {csv_path}")

        # 7. Cleanup
        record_mod.unlink(rpc, "res.partner", [pid])
        print(f"  Step 7: Deleted partner id={pid}")


# ── CLI Subprocess E2E Tests ──────────────────────────────────

def _resolve_cli(name):
    """Resolve installed CLI command; falls back to python -m for dev."""
    import shutil
    force = os.environ.get("CLI_ANYTHING_FORCE_INSTALLED", "").strip() == "1"
    path = shutil.which(name)
    if path:
        print(f"[_resolve_cli] Using installed command: {path}")
        return [path]
    if force:
        raise RuntimeError(f"{name} not found in PATH. Install with: pip install -e .")
    module = name.replace("cli-anything-", "cli_anything.") + "." + name.split("-")[-1] + "_cli"
    print(f"[_resolve_cli] Falling back to: {sys.executable} -m {module}")
    return [sys.executable, "-m", module]


# ── NLQ Ask E2E Tests ─────────────────────────────────────────

class TestNLQAsk:
    def test_ask_empresas(self, rpc):
        """Parse 'empresas' + search real Odoo."""
        parsed = nlq_mod.parse("liste todas as empresas")
        assert parsed.model == "res.company"
        result = record_mod.search(
            rpc, parsed.model, repr(parsed.domain),
            ",".join(parsed.fields), parsed.limit,
        )
        assert result["count"] >= 1
        print(f"\n  NLQ empresas: {result['count']} records")

    def test_ask_with_export(self, rpc, tmp_dir):
        """Parse + search + export CSV."""
        csv_path = os.path.join(tmp_dir, "nlq_export.csv")
        parsed = nlq_mod.parse(f"empresas e exporte para csv em {csv_path}")
        assert parsed.export is not None
        assert parsed.export.format == "csv"

        result = record_mod.search(
            rpc, parsed.model, repr(parsed.domain),
            ",".join(parsed.fields), parsed.limit,
        )
        records = result.get("records", [])
        assert len(records) >= 1

        # Do the export
        fmt_mod._do_export(parsed, records)
        assert os.path.exists(csv_path)
        size = os.path.getsize(csv_path)
        assert size > 0
        print(f"\n  NLQ export: {csv_path} ({size:,} bytes, {len(records)} records)")

    def test_ask_combined_filters(self, rpc):
        """Complex query with multiple filters against real Odoo."""
        parsed = nlq_mod.parse("parceiros ativos")
        assert parsed.model == "res.partner"
        assert ("active", "=", True) in parsed.domain

        result = record_mod.search(
            rpc, parsed.model, repr(parsed.domain),
            ",".join(parsed.fields), 5,
        )
        assert result["count"] >= 0
        print(f"\n  NLQ parceiros ativos: {result['count']} records")

    def test_ask_cross_group_pos_by_product_categ(self, rpc):
        """Cross-model grouping: POS orders by product category."""
        parsed = nlq_mod.parse("vendas do pos agrupado por categoria de produto")
        assert parsed.cross_group_key == "product_categ"

        defn = cg_mod.CROSS_GROUP_REGISTRY[("pos.order", "product_categ")]
        parent_ids = rpc.search("pos.order", parsed.domain, limit=100)
        if not parent_ids:
            pytest.skip("No pos.order records found")

        result = cg_mod.execute_cross_group(rpc, parent_ids, defn)
        assert result.grand_count > 0
        assert len(result.groups) > 0
        assert result.grand_total > 0
        print(f"\n  Cross-group POS by product categ: {len(result.groups)} groups, "
              f"{result.grand_count} lines, R$ {result.grand_total:,.2f}")
        for g in result.groups[:3]:
            print(f"    {g['name']}: {g['count']} itens, R$ {g['total']:,.2f}")

    def test_ask_cross_group_pos_by_product(self, rpc):
        """Cross-model grouping: POS orders by individual product."""
        parsed = nlq_mod.parse("vendas do pos agrupado por produto")
        assert parsed.cross_group_key == "product"

        defn = cg_mod.CROSS_GROUP_REGISTRY[("pos.order", "product")]
        parent_ids = rpc.search("pos.order", parsed.domain, limit=50)
        if not parent_ids:
            pytest.skip("No pos.order records found")

        result = cg_mod.execute_cross_group(rpc, parent_ids, defn)
        assert result.grand_count > 0
        assert len(result.groups) > 0
        print(f"\n  Cross-group POS by product: {len(result.groups)} products, "
              f"{result.grand_count} lines")


class TestCLISubprocessE2E:
    CLI_BASE = _resolve_cli("cli-anything-odoo_12")

    def _run(self, args, check=True):
        return subprocess.run(
            self.CLI_BASE + args,
            capture_output=True, text=True,
            check=check,
        )

    def test_help(self):
        result = self._run(["--help"])
        assert result.returncode == 0
        assert "Odoo 12 CLI" in result.stdout

    def test_json_record_search(self):
        """Search partners via subprocess with --json output."""
        result = self._run([
            "--json",
            "--url", ODOO_URL,
            "--db", ODOO_DB,
            "--user", ODOO_USER,
            "--password", ODOO_PASSWORD,
            "record", "search", "res.partner",
            "--limit", "3",
        ], check=False)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            assert "records" in data
            assert "count" in data
            print(f"\n  Subprocess search: {data['count']} records")

    def test_json_db_list(self):
        """List databases via subprocess with --json output."""
        result = self._run([
            "--json",
            "--url", ODOO_URL,
            "--db", ODOO_DB,
            "--user", ODOO_USER,
            "--password", ODOO_PASSWORD,
            "db", "list",
        ], check=False)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            assert "databases" in data
            print(f"\n  Subprocess db list: {data['databases']}")

    def test_json_module_list(self):
        """List modules via subprocess with --json."""
        result = self._run([
            "--json",
            "--url", ODOO_URL,
            "--db", ODOO_DB,
            "--user", ODOO_USER,
            "--password", ODOO_PASSWORD,
            "module", "list", "--installed",
        ], check=False)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            assert "modules" in data
            print(f"\n  Subprocess modules: {data['count']} installed")

    def test_full_crud_workflow(self, tmp_path):
        """Full CRUD workflow via subprocess: create → search → export → delete."""
        base_args = [
            "--json",
            "--url", ODOO_URL,
            "--db", ODOO_DB,
            "--user", ODOO_USER,
            "--password", ODOO_PASSWORD,
        ]

        # Create
        result = self._run(base_args + [
            "record", "create", "res.partner",
            "--values", '{"name":"Subprocess Test","email":"subprocess@test.com"}',
        ], check=False)
        if result.returncode != 0:
            pytest.skip(f"Cannot connect to Odoo: {result.stderr}")
        data = json.loads(result.stdout)
        pid = data["id"]
        print(f"\n  Created via subprocess: id={pid}")

        # Export to CSV
        csv_path = str(tmp_path / "subprocess_export.csv")
        result = self._run(base_args + [
            "export", "csv", "res.partner",
            "--output", csv_path,
            "--fields", "name,email",
            "--domain", f"[('id','=',{pid})]",
        ], check=False)
        if result.returncode == 0:
            assert os.path.exists(csv_path)
            size = os.path.getsize(csv_path)
            print(f"  CSV export: {csv_path} ({size:,} bytes)")

        # Delete
        result = self._run(base_args + [
            "record", "unlink", "res.partner", str(pid),
        ], check=False)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            assert data["success"] is True
            print(f"  Deleted via subprocess: id={pid}")

    def test_ask_subprocess(self):
        """NLQ ask via subprocess with --json output."""
        result = self._run([
            "--json",
            "--url", ODOO_URL,
            "--db", ODOO_DB,
            "--user", ODOO_USER,
            "--password", ODOO_PASSWORD,
            "ask", "liste todas as empresas",
        ], check=False)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            assert "records" in data
            assert "model" in data
            assert data["model"] == "res.company"
            print(f"\n  Subprocess ask: {data['count']} records")
