import csv
import psycopg2
from datetime import datetime, date
from decimal import Decimal
from proteus import config, Model


# CSV Configuration
CSV_FILE_PATH = r'C:\Users\SylvainDUVERNAY\Open Squared\Production - Documents\TRADON Implementation\ITSA\Reference Data\Loaders\Purchase_Contracts_v2.csv'

# XML-RPC Configuration
HTTPS = 'https://'
SERVER_URL = 'itsa.open-squared.tech'
DATABASE_NAME = 'tradon'
USERNAME = 'admin'
PASSWORD = 'dsproject'

# PostgreSQL Configuration (for direct database access if needed)
DB_HOST = '72.61.163.139'   
DB_PORT = 5433                      
DB_USER = 'postgres'            
DB_PASSWORD = 'dsproject'   

# Default values
DEFAULT_STATE = 'draft'
DEFAULT_INVOICE_METHOD = 'order'
DEFAULT_INVOICE_STATE = 'none'
DEFAULT_SHIPMENT_STATE = 'none'

# Import options
AUTO_ENABLE_SUPPLIER = True  # Set to False to skip auto-enabling supplier flag
SKIP_NON_SUPPLIERS = False   # Set to True to skip parties that aren't suppliers


def connect_to_tryton():
    """Establish connection to Tryton via XML-RPC"""
    print(f"Connecting to Tryton server: {SERVER_URL}")
    print(f"Database: {DATABASE_NAME}")
    print(f"Username: {USERNAME}")

    try:
        config.set_xmlrpc(f'{HTTPS}{USERNAME}:{PASSWORD}@{SERVER_URL}/{DATABASE_NAME}/')
        print("✓ Connected successfully!\n")
        return True
    except Exception as e:
        print(f"✗ Connection failed: {e}")
        print("\nTroubleshooting:")
        print("  - Verify the server URL is correct and accessible")
        print("  - Check that the Tryton server is running")
        print("  - Verify username and password are correct")
        print("  - Make sure you can access the server in a browser")
        return False


def get_db_connection():
    """Get PostgreSQL database connection"""
    try:
        conn = psycopg2.connect(
            host=DB_HOST,
            port=DB_PORT,
            database=DATABASE_NAME,
            user=DB_USER,
            password=DB_PASSWORD
        )
        return conn
    except Exception as e:
        print(f"✗ Database connection failed: {e}")
        return None


def parse_date(date_str):
    """Parse date string in various formats"""
    if not date_str or date_str.strip() == '':
        return None
    
    date_str = date_str.strip()
    
    # Handle 'NULL' or similar string values
    if date_str.upper() in ['NULL', 'NONE', 'N/A', '']:
        return None
    
    # Try different date formats
    date_formats = [
        '%Y-%m-%d',      # 2024-01-15
        '%d/%m/%Y',      # 15/01/2024
        '%m/%d/%Y',      # 01/15/2024
        '%d-%m-%Y',      # 15-01-2024
        '%Y/%m/%d',      # 2024/01/15
    ]
    
    for fmt in date_formats:
        try:
            return datetime.strptime(date_str, fmt).date()
        except ValueError:
            continue
    
    print(f"  ⚠ Warning: Could not parse date '{date_str}'")
    return None


def parse_decimal(value, field_name):
    """Parse and validate decimal value"""
    if not value or value == '':
        return None
    
    # Handle 'NULL' or similar string values
    if isinstance(value, str) and value.strip().upper() in ['NULL', 'NONE', 'N/A', '']:
        return None
    
    try:
        return Decimal(str(value))
    except (ValueError, TypeError) as e:
        print(f"  ⚠ Warning: Invalid {field_name} value '{value}' - {e}")
        return None


def list_available_currencies():
    """List all available currencies for debugging"""
    Currency = Model.get('currency.currency')
    currencies = Currency.find([])
    
    print("\n" + "="*70)
    print("AVAILABLE CURRENCIES")
    print("="*70)
    print(f"{'Name':<10} {'Code':<10} {'Symbol':<10}")
    print("-"*70)
    
    for curr in currencies:
        name = curr.name if hasattr(curr, 'name') else 'N/A'
        code = str(curr.code) if hasattr(curr, 'code') else 'N/A'
        symbol = curr.symbol if hasattr(curr, 'symbol') else 'N/A'
        print(f"{name:<10} {code:<10} {symbol:<10}")
    
    print("="*70 + "\n")
    return currencies


def list_available_payment_terms():
    """List all available payment terms for debugging"""
    PaymentTerm = Model.get('account.invoice.payment_term')
    terms = PaymentTerm.find([])
    
    print("="*70)
    print("AVAILABLE PAYMENT TERMS")
    print("="*70)
    if terms:
        print(f"{'Name':<50}")
        print("-"*70)
        for term in terms:
            name = term.name if hasattr(term, 'name') else 'N/A'
            print(f"{name:<50}")
    else:
        print("No payment terms found")
    print("="*70 + "\n")
    return terms


def diagnose_incoterm_column():
    """Diagnose the incoterm column in purchase_purchase table"""
    conn = get_db_connection()
    if not conn:
        print("Cannot connect to database for diagnosis")
        return
    
    try:
        cursor = conn.cursor()
        
        print("\n" + "="*70)
        print("INCOTERM COLUMN DIAGNOSIS")
        print("="*70)
        
        # Check if incoterm column exists and its properties
        cursor.execute("""
            SELECT 
                column_name, 
                data_type, 
                is_nullable,
                column_default,
                character_maximum_length
            FROM information_schema.columns 
            WHERE table_name = 'purchase_purchase'
            AND column_name = 'incoterm'
        """)
        
        result = cursor.fetchone()
        if result:
            print("✓ Incoterm column EXISTS in purchase_purchase table:")
            print(f"  Column Name: {result[0]}")
            print(f"  Data Type: {result[1]}")
            print(f"  Nullable: {result[2]}")
            print(f"  Default: {result[3]}")
            print(f"  Max Length: {result[4]}")
        else:
            print("✗ Incoterm column NOT FOUND in purchase_purchase table!")
        
        # First, check what columns exist in incoterm_incoterm table
        print("\nColumns in incoterm_incoterm table:")
        cursor.execute("""
            SELECT column_name, data_type
            FROM information_schema.columns 
            WHERE table_name = 'incoterm_incoterm'
            ORDER BY ordinal_position
        """)
        
        columns = cursor.fetchall()
        for col in columns:
            print(f"  - {col[0]} ({col[1]})")
        
        # Check sample incoterms available (using only columns that exist)
        print("\nSample incoterms in incoterm_incoterm table:")
        cursor.execute("""
            SELECT id, code, version, active
            FROM incoterm_incoterm 
            ORDER BY code, version DESC
            LIMIT 10
        """)
        
        incoterms = cursor.fetchall()
        if incoterms:
            print(f"{'ID':<10} {'Code':<15} {'Version':<10} {'Active':<10}")
            print("-"*70)
            for inc in incoterms:
                print(f"{inc[0]:<10} {inc[1]:<15} {inc[2]:<10} {inc[3]}")
        else:
            print("No incoterms found in table")
        
        print("="*70 + "\n")
        
        cursor.close()
        conn.close()
        
    except Exception as e:
        print(f"Error during diagnosis: {e}")
        import traceback
        traceback.print_exc()
        if conn:
            conn.close()


def get_supplier_category():
    """Get or find the SUPPLIER category"""
    Category = Model.get('party.category')
    
    # Try to find existing SUPPLIER category
    categories = Category.find([('name', '=', 'SUPPLIER')])
    if categories:
        return categories[0]
    
    # Try case-insensitive
    all_categories = Category.find([])
    for cat in all_categories:
        if cat.name.upper() == 'SUPPLIER':
            return cat
    
    print(f"  ⚠ Warning: SUPPLIER category not found")
    return None


def ensure_party_is_supplier(party, auto_enable=True):
    """Check if party has SUPPLIER category, optionally add it"""
    if not party:
        return False
    
    # Get the SUPPLIER category
    supplier_category = get_supplier_category()
    if not supplier_category:
        print(f"    ✗ Cannot find SUPPLIER category in the system")
        return False
    
    # Check if party already has SUPPLIER category
    has_supplier_category = False
    if hasattr(party, 'categories') and party.categories:
        for category in party.categories:
            if category.id == supplier_category.id:
                has_supplier_category = True
                break
    
    if has_supplier_category:
        return True
    
    if not auto_enable:
        print(f"    ⚠ Party '{party.rec_name}' does not have SUPPLIER category")
        print(f"    Please manually add SUPPLIER category to this party in Tryton UI")
        return False
    
    # Try to add SUPPLIER category
    print(f"    Party '{party.rec_name}' does not have SUPPLIER category - adding it")
    try:
        # Reload the party to make sure we have the latest data
        Party = Model.get('party.party')
        party_to_update = Party(party.id)
        
        # Get existing categories
        existing_categories = list(party_to_update.categories) if party_to_update.categories else []
        
        # Add SUPPLIER category
        existing_categories.append(supplier_category)
        party_to_update.categories = existing_categories
        
        party_to_update.save()
        print(f"    ✓ SUPPLIER category added to party")
        
        # Update the original party object
        party.categories = existing_categories
        return True
        
    except Exception as e:
        print(f"    ✗ Could not add SUPPLIER category: {e}")
        print(f"    Error type: {type(e).__name__}")
        import traceback
        traceback.print_exc()
        
        # Check if it's a permission issue
        if 'permission' in str(e).lower() or 'access' in str(e).lower():
            print(f"    This appears to be a permission issue.")
            print(f"    Please manually add SUPPLIER category to '{party.rec_name}' in Tryton UI")
        return False


def find_party(party_code, company=None):
    """Find party by code"""
    Party = Model.get('party.party')
    
    # Try by code first
    parties = Party.find([('code', '=', party_code)])
    if parties:
        party = parties[0]
        return party
    
    # Try by name as fallback
    parties = Party.find([('name', '=', party_code)])
    if parties:
        party = parties[0]
        print(f"    Found party '{party_code}' by name instead of code")
        return party
    
    print(f"  ⚠ Warning: Party with code '{party_code}' not found")
    return None


def find_currency(currency_code):
    """Find currency by name or code"""
    Currency = Model.get('currency.currency')
    
    if not currency_code:
        print(f"  ⚠ Warning: No currency code provided")
        return None
    
    # Try multiple approaches to find the currency
    # 1. Try by name (most common: 'USD', 'EUR', etc.)
    try:
        currencies = Currency.find([('name', '=', currency_code)])
        if currencies:
            return currencies[0]
    except Exception as e:
        print(f"    Debug: Search by name failed: {e}")
    
    # 2. Try case-insensitive name search
    try:
        all_currencies = Currency.find([])
        for curr in all_currencies:
            if hasattr(curr, 'name') and curr.name and curr.name.upper() == currency_code.upper():
                return curr
    except Exception as e:
        print(f"    Debug: Case-insensitive search failed: {e}")
    
    # 3. Try by code (in case it's numeric like '1', '2', etc.)
    try:
        currencies = Currency.find([('code', '=', currency_code)])
        if currencies:
            return currencies[0]
    except Exception as e:
        print(f"    Debug: Search by code failed: {e}")
    
    print(f"  ⚠ Warning: Currency '{currency_code}' not found")
    return None


def find_location(location_code):
    """Find stock location by code or name"""
    Location = Model.get('stock.location')
    
    if not location_code:
        return None
    
    # Try by code first
    locations = Location.find([('code', '=', location_code)])
    if locations:
        return locations[0]
    
    # Try by name as fallback
    locations = Location.find([('name', '=', location_code)])
    if locations:
        print(f"    Found location '{location_code}' by name instead of code")
        return locations[0]
    
    print(f"  ⚠ Warning: Location with code '{location_code}' not found")
    return None


def find_warehouse(warehouse_code):
    """Find warehouse by code or name"""
    Location = Model.get('stock.location')
    
    if not warehouse_code:
        return None
    
    # Try by code first
    warehouses = Location.find([
        ('code', '=', warehouse_code),
        ('type', '=', 'warehouse')
    ])
    if warehouses:
        return warehouses[0]
    
    # Try by name as fallback
    warehouses = Location.find([
        ('name', '=', warehouse_code),
        ('type', '=', 'warehouse')
    ])
    if warehouses:
        print(f"    Found warehouse '{warehouse_code}' by name instead of code")
        return warehouses[0]
    
    print(f"  ⚠ Warning: Warehouse with code '{warehouse_code}' not found")
    return None


def find_payment_term(payment_term_name):
    """Find payment term by name"""
    PaymentTerm = Model.get('account.invoice.payment_term')
    
    if not payment_term_name:
        return None
    
    terms = PaymentTerm.find([('name', '=', payment_term_name)])
    if terms:
        return terms[0]
    
    # Try case-insensitive
    all_terms = PaymentTerm.find([])
    for term in all_terms:
        if term.name.upper() == payment_term_name.upper():
            print(f"    Found payment term '{payment_term_name}' (case-insensitive)")
            return term
    
    print(f"  ⚠ Warning: Payment term '{payment_term_name}' not found")
    return None


def find_product(product_code):
    """Find product by code"""
    Product = Model.get('product.product')
    
    if not product_code:
        return None
    
    # Try by code first
    products = Product.find([('code', '=', product_code)])
    if products:
        return products[0]
    
    # Try by template code as fallback
    Template = Model.get('product.template')
    templates = Template.find([('code', '=', product_code)])
    if templates and templates[0].products:
        print(f"    Found product '{product_code}' via template")
        return templates[0].products[0]
    
    print(f"  ⚠ Warning: Product with code '{product_code}' not found")
    return None


def find_uom(uom_code):
    """Find unit of measure by code or symbol"""
    Uom = Model.get('product.uom')
    
    if not uom_code:
        return None
    
    # Try by symbol first (most common: 'kg', 'unit', etc.)
    uoms = Uom.find([('symbol', '=', uom_code)])
    if uoms:
        return uoms[0]
    
    # Try by name
    uoms = Uom.find([('name', '=', uom_code)])
    if uoms:
        print(f"    Found UOM '{uom_code}' by name instead of symbol")
        return uoms[0]
    
    # Try case-insensitive
    all_uoms = Uom.find([])
    for uom in all_uoms:
        if (hasattr(uom, 'symbol') and uom.symbol and uom.symbol.upper() == uom_code.upper()):
            print(f"    Found UOM '{uom_code}' (case-insensitive)")
            return uom
    
    print(f"  ⚠ Warning: UOM '{uom_code}' not found")
    return None


def find_incoterm(incoterm_name):
    """Find incoterm by code and return its ID (prefer 2025 version)"""
    if not incoterm_name:
        return None
    
    conn = get_db_connection()
    if not conn:
        print(f"  ⚠ Warning: Cannot find incoterm - database connection failed")
        return None
    
    try:
        cursor = conn.cursor()
        
        # Try to find incoterm by code
        # Prefer 2025 version, then latest version if 2025 not available
        cursor.execute("""
            SELECT id, code, version 
            FROM incoterm_incoterm 
            WHERE UPPER(code) = UPPER(%s)
            AND active = true
            ORDER BY 
                CASE WHEN version = '2025' THEN 0 ELSE 1 END,
                version DESC
            LIMIT 1
        """, (incoterm_name,))
        
        result = cursor.fetchone()
        cursor.close()
        conn.close()
        
        if result:
            incoterm_id, code, version = result
            print(f"    Found incoterm: {code} (version {version}) - ID: {incoterm_id}")
            return incoterm_id
        else:
            print(f"  ⚠ Warning: Incoterm '{incoterm_name}' not found")
            return None
            
    except Exception as e:
        print(f"  ⚠ Error finding incoterm: {e}")
        import traceback
        traceback.print_exc()
        if conn:
            conn.close()
        return None


def get_party_invoice_address(party):
    """Get invoice address from party"""
    if not party:
        return None
    
    # Try to get addresses
    if hasattr(party, 'addresses') and party.addresses:
        # Look for an invoice address first
        for address in party.addresses:
            if hasattr(address, 'invoice') and address.invoice:
                return address
        
        # If no invoice address, return first address
        return party.addresses[0]
    
    return None


def check_purchase_exists(reference, party):
    """Check if purchase with given reference and party already exists"""
    Purchase = Model.get('purchase.purchase')
    purchases = Purchase.find([
        ('reference', '=', reference),
        ('party', '=', party.id)
    ])
    return purchases[0] if purchases else None


def update_purchase_custom_fields(purchase_id, custom_data):
    """Update custom fields in purchase using direct SQL"""
    if not custom_data:
        return True
    
    conn = get_db_connection()
    if not conn:
        print(f"  ⚠ Could not update custom fields - database connection failed")
        return False
    
    try:
        cursor = conn.cursor()
        
        # First, check what columns exist in purchase_purchase table
        cursor.execute("""
            SELECT column_name, data_type 
            FROM information_schema.columns 
            WHERE table_name = 'purchase_purchase'
            AND column_name IN ('wb', 'tol_min', 'tol_max', 'from_location', 'to_location', 'incoterm')
            ORDER BY column_name
        """)
        existing_columns = {row[0]: row[1] for row in cursor.fetchall()}
        
        print(f"    Available custom columns in purchase_purchase:")
        for col_name, col_type in existing_columns.items():
            print(f"      - {col_name} ({col_type})")
        
        # Build UPDATE query for custom fields
        set_clauses = []
        values = []
        
        if 'wb' in existing_columns and custom_data.get('wb') is not None:
            set_clauses.append("wb = %s")
            values.append(float(custom_data['wb']))
            print(f"    Adding wb = {float(custom_data['wb'])}")
        
        if 'tol_min' in existing_columns and custom_data.get('tol_min') is not None:
            set_clauses.append("tol_min = %s")
            values.append(float(custom_data['tol_min']))
            print(f"    Adding tol_min = {float(custom_data['tol_min'])}")
        
        if 'tol_max' in existing_columns and custom_data.get('tol_max') is not None:
            set_clauses.append("tol_max = %s")
            values.append(float(custom_data['tol_max']))
            print(f"    Adding tol_max = {float(custom_data['tol_max'])}")
        
        if 'from_location' in existing_columns and custom_data.get('from_location'):
            set_clauses.append("from_location = %s")
            values.append(custom_data['from_location'])
            print(f"    Adding from_location = {custom_data['from_location']}")
        
        if 'to_location' in existing_columns and custom_data.get('to_location'):
            set_clauses.append("to_location = %s")
            values.append(custom_data['to_location'])
            print(f"    Adding to_location = {custom_data['to_location']}")
        
        if 'incoterm' in existing_columns and custom_data.get('incoterm'):
            set_clauses.append("incoterm = %s")
            values.append(custom_data['incoterm'])
            print(f"    Adding incoterm = {custom_data['incoterm']}")
        elif custom_data.get('incoterm'):
            print(f"    ⚠ WARNING: incoterm column NOT FOUND in table, but we have incoterm value: {custom_data['incoterm']}")
        
        if set_clauses:
            values.append(purchase_id)
            update_query = f"""
                UPDATE purchase_purchase 
                SET {', '.join(set_clauses)}
                WHERE id = %s
            """
            
            print(f"    Executing UPDATE with fields: {', '.join([c.split('=')[0].strip() for c in set_clauses])}")
            print(f"    SQL: {update_query}")
            print(f"    Values: {values}")
            
            cursor.execute(update_query, values)
            rows_affected = cursor.rowcount
            conn.commit()
            
            if rows_affected > 0:
                print(f"    ✓ {rows_affected} row(s) updated successfully")
                
                # Verify the update by reading back
                verify_columns = ', '.join(existing_columns.keys())
                cursor.execute(f"""
                    SELECT {verify_columns}
                    FROM purchase_purchase
                    WHERE id = %s
                """, (purchase_id,))
                verify = cursor.fetchone()
                if verify:
                    print(f"    Verification after update:")
                    for i, col_name in enumerate(existing_columns.keys()):
                        print(f"      {col_name}: {verify[i]}")
            else:
                print(f"    ⚠ No rows updated (purchase_id={purchase_id} not found?)")
        else:
            print(f"    No custom fields to update (either no data provided or columns not found)")
        
        cursor.close()
        conn.close()
        return True
        
    except Exception as e:
        print(f"  ⚠ Error updating custom fields: {e}")
        import traceback
        traceback.print_exc()
        if conn:
            conn.rollback()
            conn.close()
        return False


def update_line_custom_fields(line_id, custom_data):
    """Update custom fields in purchase line using direct SQL"""
    if not custom_data:
        return True
    
    conn = get_db_connection()
    if not conn:
        print(f"  ⚠ Could not update line custom fields - database connection failed")
        return False
    
    try:
        cursor = conn.cursor()
        
        # Build UPDATE query for custom fields
        set_clauses = []
        values = []
        
        if custom_data.get('from_del'):
            set_clauses.append("from_del = %s")
            values.append(custom_data['from_del'])
        
        if custom_data.get('to_del'):
            set_clauses.append("to_del = %s")
            values.append(custom_data['to_del'])
        
        if set_clauses:
            values.append(line_id)
            update_query = f"""
                UPDATE purchase_line 
                SET {', '.join(set_clauses)}
                WHERE id = %s
            """
            
            cursor.execute(update_query, values)
            conn.commit()
        
        cursor.close()
        conn.close()
        return True
        
    except Exception as e:
        print(f"  ⚠ Error updating line custom fields: {e}")
        import traceback
        traceback.print_exc()
        if conn:
            conn.rollback()
            conn.close()
        return False


def create_pricing_estimated(line_id, pricing_data):
    """Create pricing_estimated record using direct SQL"""
    if not pricing_data or not pricing_data.get('trigger'):
        return True
    
    conn = get_db_connection()
    if not conn:
        print(f"  ⚠ Could not create pricing estimate - database connection failed")
        return False
    
    try:
        cursor = conn.cursor()
        
        # Insert into pricing_estimated table
        insert_query = """
            INSERT INTO pricing_estimated (line, trigger, estimated_date, create_date, write_date, create_uid, write_uid)
            VALUES (%s, %s, %s, NOW(), NOW(), 1, 1)
        """
        
        cursor.execute(insert_query, (
            line_id,
            pricing_data['trigger'],
            pricing_data.get('estimated_date')
        ))
        
        conn.commit()
        cursor.close()
        conn.close()
        return True
        
    except Exception as e:
        print(f"  ⚠ Error creating pricing estimate: {e}")
        import traceback
        traceback.print_exc()
        if conn:
            conn.rollback()
            conn.close()
        return False


def create_purchase(row):
    """Create a new purchase using proteus"""
    Purchase = Model.get('purchase.purchase')
    
    # Create purchase header
    purchase = Purchase()
    
    # Set company first (required for domain evaluation)
    purchase.company = row['company']
    
    # Required fields
    purchase.party = row['party']
    purchase.currency = row['currency']
    
    # Optional header fields
    if row.get('reference'):
        purchase.reference = row['reference']
    
    if row.get('description'):
        purchase.description = row['description']
    
    if row.get('purchase_date'):
        purchase.purchase_date = row['purchase_date']
    
    if row.get('warehouse'):
        purchase.warehouse = row['warehouse']
    
    if row.get('payment_term'):
        purchase.payment_term = row['payment_term']
    
    if row.get('invoice_method'):
        purchase.invoice_method = row['invoice_method']
    else:
        purchase.invoice_method = DEFAULT_INVOICE_METHOD
    
    if row.get('invoice_address'):
        purchase.invoice_address = row['invoice_address']
    elif row['party']:
        # Get default invoice address from party
        invoice_address = get_party_invoice_address(row['party'])
        if invoice_address:
            purchase.invoice_address = invoice_address
    
    if row.get('comment'):
        purchase.comment = row['comment']
    
    # State and status fields
    purchase.state = row.get('state', DEFAULT_STATE)
    purchase.invoice_state = DEFAULT_INVOICE_STATE
    purchase.shipment_state = DEFAULT_SHIPMENT_STATE
    
    # Save purchase header first
    purchase.save()
    
    # Update custom fields via SQL if provided
    custom_data = {}
    if row.get('wb') is not None:
        custom_data['wb'] = row['wb']
    if row.get('tol_min') is not None:
        custom_data['tol_min'] = row['tol_min']
    if row.get('tol_max') is not None:
        custom_data['tol_max'] = row['tol_max']
    if row.get('from_location') is not None:
        custom_data['from_location'] = row['from_location']
    if row.get('to_location') is not None:
        custom_data['to_location'] = row['to_location']
    if row.get('incoterm') is not None:
        custom_data['incoterm'] = row['incoterm']
    
    if custom_data:
        update_purchase_custom_fields(purchase.id, custom_data)
    
    return purchase


def create_purchase_line(purchase, line_data):
    """Create a purchase line"""
    Line = Model.get('purchase.line')
    
    line = Line()
    line.purchase = purchase
    line.type = line_data.get('type', 'line')
    
    if line.type == 'line':
        # Product line
        line.product = line_data['product']
        line.quantity = line_data['quantity']
        
        # Unit - use provided or default from product
        if line_data.get('unit'):
            line.unit = line_data['unit']
        else:
            line.unit = line_data['product'].purchase_uom
        
        line.unit_price = line_data['unit_price']
        
        if line_data.get('description'):
            line.description = line_data['description']
        
        # Set taxes if provided
        if line_data.get('taxes'):
            line.taxes = line_data['taxes']
        
        # Delivery date
        if line_data.get('delivery_date'):
            line.delivery_date_edit = True
            line.delivery_date_store = line_data['delivery_date']
    
    elif line.type in ['comment', 'title', 'subtotal']:
        # Non-product lines
        if line_data.get('description'):
            line.description = line_data['description']
    
    line.save()
    
    # Update custom fields via SQL if provided
    custom_data = {}
    if line_data.get('from_del'):
        custom_data['from_del'] = line_data['from_del']
    if line_data.get('to_del'):
        custom_data['to_del'] = line_data['to_del']
    
    if custom_data:
        update_line_custom_fields(line.id, custom_data)
    
    # Create pricing estimate if provided
    pricing_data = {}
    if line_data.get('pricing_trigger'):
        pricing_data['trigger'] = line_data['pricing_trigger']
        pricing_data['estimated_date'] = line_data.get('pricing_estimated_date')
        create_pricing_estimated(line.id, pricing_data)
    
    return line


def import_purchases(csv_file):
    """Import purchases from CSV file"""
    
    imported_count = 0
    skipped_count = 0
    error_count = 0
    errors = []
    
    # Get company (assuming single company or default)
    Company = Model.get('company.company')
    companies = Company.find([])
    if not companies:
        print("✗ Error: No company found in the system")
        return
    company = companies[0]
    print(f"Using company: {company.rec_name}\n")
    
    # List available currencies for debugging
    available_currencies = list_available_currencies()
    
    # List available payment terms for debugging
    available_payment_terms = list_available_payment_terms()
    
    print(f"{'='*70}")
    print(f"Importing purchases from: {csv_file}")
    print(f"{'='*70}\n")
    
    try:
        # Open with utf-8-sig to handle BOM
        with open(csv_file, 'r', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            
            # Debug: Show detected columns
            print(f"Detected columns: {reader.fieldnames}\n")
            
            current_purchase = None
            current_reference = None
            
            for row_num, row in enumerate(reader, start=2):
                try:
                    # Clean up values
                    reference = row.get('reference', '').strip()
                    party_code = row.get('party_code', '').strip()
                    
                    # Skip empty rows
                    if not reference and not party_code:
                        continue
                    
                    print(f"Processing Row {row_num}: Reference '{reference}'")
                    
                    # Check if this is a new purchase or a line for existing purchase
                    if reference and reference != current_reference:
                        # New purchase header
                        current_reference = reference
                        
                        # Find required entities
                        party = find_party(party_code, company)
                        if not party:
                            error_msg = f"Row {row_num}: Party '{party_code}' not found"
                            errors.append(error_msg)
                            error_count += 1
                            current_purchase = None
                            continue
                        
                        # Debug: Show party details
                        print(f"  Found party: {party.rec_name}")
                        if hasattr(party, 'code'):
                            print(f"    Code: {party.code}")
                        if hasattr(party, 'id'):
                            print(f"    ID: {party.id}")
                        if hasattr(party, 'categories') and party.categories:
                            category_names = [cat.name for cat in party.categories]
                            print(f"    Categories: {', '.join(category_names)}")
                            has_supplier = any(cat.name.upper() == 'SUPPLIER' for cat in party.categories)
                            print(f"    Has SUPPLIER category: {has_supplier}")
                        else:
                            print(f"    Categories: None")
                        
                        # Ensure party has SUPPLIER category
                        if not ensure_party_is_supplier(party, auto_enable=AUTO_ENABLE_SUPPLIER):
                            if SKIP_NON_SUPPLIERS:
                                print(f"  ⚠ Skipping purchase - party does not have SUPPLIER category\n")
                                skipped_count += 1
                                current_purchase = None
                                continue
                            else:
                                error_msg = f"Row {row_num}: Party '{party.rec_name}' does not have SUPPLIER category"
                                errors.append(error_msg)
                                error_count += 1
                                current_purchase = None
                                continue
                        
                        # Check if purchase already exists
                        existing_purchase = check_purchase_exists(reference, party)
                        if existing_purchase:
                            print(f"  ⚠ Purchase '{reference}' for party '{party.rec_name}' already exists (ID: {existing_purchase.id})")
                            print(f"    Skipping...\n")
                            skipped_count += 1
                            current_purchase = existing_purchase
                            continue
                        
                        # Find currency
                        currency_code = row.get('currency_code', '').strip() or 'USD'
                        print(f"  Looking for currency: '{currency_code}'")
                        currency = find_currency(currency_code)
                        if not currency:
                            error_msg = f"Row {row_num}: Currency '{currency_code}' not found"
                            errors.append(error_msg)
                            error_count += 1
                            current_purchase = None
                            continue
                        
                        # Optional fields
                        warehouse = None
                        warehouse_code = row.get('warehouse_code', '').strip()
                        if warehouse_code:
                            warehouse = find_warehouse(warehouse_code)
                        
                        payment_term = None
                        payment_term_name = row.get('payment_term', '').strip()
                        if payment_term_name:
                            payment_term = find_payment_term(payment_term_name)
                            # Payment term is optional, so continue even if not found
                            if not payment_term:
                                print(f"    Continuing without payment term")
                        
                        # Parse dates
                        purchase_date = parse_date(row.get('purchase_date', ''))
                        
                        # Parse custom numeric fields
                        wb = parse_decimal(row.get('wb', ''), 'wb')
                        tol_min = parse_decimal(row.get('tol_min', ''), 'tol_min')
                        tol_max = parse_decimal(row.get('tol_max', ''), 'tol_max')
                        
                        # Get locations by name
                        from_location = None
                        from_location_name = row.get('from_location_name', '').strip()
                        if from_location_name:
                            from_location_obj = find_location(from_location_name)
                            if from_location_obj:
                                from_location = from_location_obj.id
                        
                        to_location = None
                        to_location_name = row.get('to_location_name', '').strip()
                        if to_location_name:
                            to_location_obj = find_location(to_location_name)
                            if to_location_obj:
                                to_location = to_location_obj.id
                        
                        # Get incoterm by name
                        incoterm = None
                        incoterm_name = row.get('incoterm_name', '').strip()
                        if incoterm_name:
                            incoterm = find_incoterm(incoterm_name)
                        
                        # Prepare purchase data
                        purchase_data = {
                            'reference': reference,
                            'party': party,
                            'company': company,
                            'currency': currency,
                            'purchase_date': purchase_date,
                            'warehouse': warehouse,
                            'payment_term': payment_term,
                            'invoice_method': row.get('invoice_method', '').strip() or DEFAULT_INVOICE_METHOD,
                            'description': row.get('description', '').strip(),
                            'comment': row.get('comment', '').strip(),
                            'state': row.get('state', '').strip() or DEFAULT_STATE,
                            'wb': wb,
                            'tol_min': tol_min,
                            'tol_max': tol_max,
                            'from_location': from_location,
                            'to_location': to_location,
                            'incoterm': incoterm,
                        }
                        
                        # Create the purchase
                        current_purchase = create_purchase(purchase_data)
                        
                        print(f"  ✓ Created purchase header")
                        print(f"    Purchase ID: {current_purchase.id}")
                        print(f"    Reference: {reference}")
                        print(f"    Party: {party.rec_name}")
                        print(f"    Currency: {currency.name if hasattr(currency, 'name') else currency.code}")
                        if purchase_date:
                            print(f"    Purchase Date: {purchase_date}")
                        if wb is not None:
                            print(f"    WB: {wb}")
                        if tol_min is not None or tol_max is not None:
                            print(f"    Tolerances: Min={tol_min}, Max={tol_max}")
                        if from_location:
                            print(f"    From Location ID: {from_location}")
                        if to_location:
                            print(f"    To Location ID: {to_location}")
                        if incoterm:
                            print(f"    Incoterm ID: {incoterm}")
                        
                        imported_count += 1
                    
                    # Create purchase line if we have a current purchase and product data
                    line_product_code = row.get('line_product_code', '').strip()
                    line_type = row.get('line_type', '').strip() or 'line'
                    
                    if current_purchase and (line_product_code or line_type != 'line'):
                        
                        if line_type == 'line':
                            # Product line
                            product = find_product(line_product_code)
                            if not product:
                                print(f"  ⚠ Warning: Product '{line_product_code}' not found, skipping line")
                                continue
                            
                            # Parse line data
                            quantity = parse_decimal(row.get('line_quantity', ''), 'quantity')
                            if quantity is None:
                                print(f"  ⚠ Warning: Invalid quantity, skipping line")
                                continue
                            
                            unit_price = parse_decimal(row.get('line_unit_price', ''), 'unit_price')
                            if unit_price is None:
                                unit_price = Decimal('0')
                            
                            # Parse delivery dates
                            from_del = parse_date(row.get('line_from_del', ''))
                            to_del = parse_date(row.get('line_to_del', ''))
                            
                            # Parse pricing estimate data
                            pricing_trigger = row.get('pricing_trigger', '').strip()
                            pricing_estimated_date = parse_date(row.get('pricing_estimated_date', ''))
                            
                            # Find UOM if specified
                            unit = None
                            line_unit_code = row.get('line_unit_code', '').strip()
                            if line_unit_code:
                                unit = find_uom(line_unit_code)
                            
                            line_data = {
                                'type': 'line',
                                'product': product,
                                'quantity': quantity,
                                'unit': unit,
                                'unit_price': unit_price,
                                'description': row.get('line_description', '').strip(),
                                'from_del': from_del,
                                'to_del': to_del,
                                'pricing_trigger': pricing_trigger,
                                'pricing_estimated_date': pricing_estimated_date,
                            }
                        
                        else:
                            # Non-product line (comment, title, subtotal)
                            line_data = {
                                'type': line_type,
                                'description': row.get('line_description', '').strip(),
                            }
                        
                        # Create the line
                        line = create_purchase_line(current_purchase, line_data)
                        
                        print(f"    ✓ Added line")
                        if line_type == 'line':
                            print(f"      Product: {product.rec_name}")
                            print(f"      Quantity: {quantity}")
                            if unit:
                                print(f"      Unit: {unit.symbol}")
                            print(f"      Unit Price: {unit_price}")
                            if from_del or to_del:
                                print(f"      Delivery: {from_del} to {to_del}")
                            if pricing_trigger:
                                print(f"      Pricing: {pricing_trigger} ({pricing_estimated_date})")
                        else:
                            print(f"      Type: {line_type}")
                    
                    print()
                    
                except Exception as e:
                    error_msg = f"Row {row_num} - {reference if 'reference' in locals() else 'Unknown'}: {str(e)}"
                    errors.append(error_msg)
                    error_count += 1
                    print(f"✗ Error on row {row_num}: {e}\n")
                    import traceback
                    traceback.print_exc()
        
        # Summary
        print(f"{'='*70}")
        print("IMPORT SUMMARY")
        print(f"{'='*70}")
        print(f"Successfully imported: {imported_count} purchases")
        print(f"Skipped (already exist): {skipped_count} purchases")
        print(f"Errors: {error_count}")
        
        if errors:
            print(f"\nError details:")
            for error in errors:
                print(f"  - {error}")
        
        print(f"\n{'='*70}")
        
    except FileNotFoundError:
        print(f"✗ Error: CSV file not found at {csv_file}")
        print(f"Please update CSV_FILE_PATH in the script with the correct path.")
    except Exception as e:
        print(f"✗ Fatal error: {e}")
        import traceback
        traceback.print_exc()


def verify_import():
    """Verify imported purchases"""
    Purchase = Model.get('purchase.purchase')
    
    print(f"\n{'='*70}")
    print("VERIFICATION - Purchases")
    print(f"{'='*70}\n")
    
    # Find all purchases (or limit to recently created ones)
    purchases = Purchase.find([], order=[('id', 'DESC')])
    
    if purchases:
        print(f"Found {len(purchases)} purchases (showing last 10):\n")
        print(f"{'ID':<8} {'Reference':<20} {'Party':<30} {'State':<12} {'Total':<15}")
        print("-" * 90)
        
        for purchase in purchases[:10]:  # Show last 10 created
            purchase_id = purchase.id
            reference = purchase.reference[:19] if purchase.reference else 'N/A'
            party = purchase.party.rec_name[:29] if purchase.party else 'N/A'
            state = purchase.state if purchase.state else 'N/A'
            total = f"{purchase.total_amount:.2f}" if purchase.total_amount else 'N/A'
            
            print(f"{purchase_id:<8} {reference:<20} {party:<30} {state:<12} {total:<15}")
            
            # Show lines
            if purchase.lines:
                print(f"  Lines: {len(purchase.lines)}")
                for line in purchase.lines[:3]:  # Show first 3 lines
                    if line.type == 'line' and line.product:
                        print(f"    - {line.product.rec_name[:40]} | Qty: {line.quantity} | Price: {line.unit_price}")
                    else:
                        print(f"    - [{line.type}] {(line.description or '')[:40]}")
    else:
        print("No purchases found")
    
    print()


def prepare_parties_as_suppliers(csv_file):
    """Pre-process: Add SUPPLIER category to all parties in CSV"""
    Party = Model.get('party.party')
    
    print(f"{'='*70}")
    print(f"PREPARING PARTIES AS SUPPLIERS (via Categories)")
    print(f"{'='*70}\n")
    
    # Get SUPPLIER category
    supplier_category = get_supplier_category()
    if not supplier_category:
        print(f"✗ SUPPLIER category not found in the system!")
        print(f"Please create a party category named 'SUPPLIER' first.\n")
        return False
    
    print(f"Found SUPPLIER category (ID: {supplier_category.id})\n")
    
    party_codes = set()
    
    try:
        with open(csv_file, 'r', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            for row in reader:
                party_code = row.get('party_code', '').strip()
                if party_code:
                    party_codes.add(party_code)
        
        print(f"Found {len(party_codes)} unique parties in CSV\n")
        
        updated_count = 0
        already_supplier_count = 0
        not_found_count = 0
        
        for party_code in party_codes:
            print(f"Processing party: {party_code}")
            
            # Find party
            parties = Party.find([('code', '=', party_code)])
            if not parties:
                parties = Party.find([('name', '=', party_code)])
            
            if not parties:
                print(f"  ✗ Not found\n")
                not_found_count += 1
                continue
            
            party = parties[0]
            
            # Check if already has SUPPLIER category
            has_supplier = False
            if party.categories:
                for cat in party.categories:
                    if cat.id == supplier_category.id:
                        has_supplier = True
                        break
            
            if has_supplier:
                print(f"  ✓ Already has SUPPLIER category\n")
                already_supplier_count += 1
                continue
            
            # Add SUPPLIER category
            try:
                party_to_update = Party(party.id)
                existing_categories = list(party_to_update.categories) if party_to_update.categories else []
                existing_categories.append(supplier_category)
                party_to_update.categories = existing_categories
                party_to_update.save()
                print(f"  ✓ SUPPLIER category added\n")
                updated_count += 1
            except Exception as e:
                print(f"  ✗ Failed: {e}\n")
        
        print(f"{'='*70}")
        print(f"PREPARATION SUMMARY")
        print(f"{'='*70}")
        print(f"Already have SUPPLIER category: {already_supplier_count}")
        print(f"SUPPLIER category added: {updated_count}")
        print(f"Not found: {not_found_count}")
        print(f"{'='*70}\n")
        
        return True
        
    except FileNotFoundError:
        print(f"✗ Error: CSV file not found at {csv_file}\n")
        return False
    except Exception as e:
        print(f"✗ Error: {e}\n")
        import traceback
        traceback.print_exc()
        return False


def main():
    print("="*70)
    print("TRYTON PURCHASE IMPORT SCRIPT")
    print("Using Proteus with XML-RPC Connection")
    print("="*70)
    print()
    
    # Connect to Tryton using XML-RPC
    if not connect_to_tryton():
        return 1
    
    # Run diagnostics
    diagnose_incoterm_column()
    
    # Optional: Prepare all parties as suppliers first
    # Uncomment the following line to mark all parties in CSV as suppliers before importing
    # prepare_parties_as_suppliers(CSV_FILE_PATH)
    
    # Import purchases
    import_purchases(CSV_FILE_PATH)
    
    # Verify import
    verify_import()
    
    return 0


if __name__ == '__main__':
    exit(main())