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\Sale_Contracts.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_CLIENT = True  # Set to False to skip auto-enabling client flag
SKIP_NON_CLIENTS = False   # Set to True to skip parties that aren't clients


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 ensure_party_is_client(party, auto_enable=True):
    """Check if party has CLIENT category, optionally add it"""
    if not party:
        return False
    
    Category = Model.get('party.category')
    
    # Check if party already has CLIENT category
    has_client_category = False
    if hasattr(party, 'categories') and party.categories:
        for category in party.categories:
            if category.name.upper() == 'CLIENT':
                has_client_category = True
                break
    
    if has_client_category:
        return True
    
    if not auto_enable:
        print(f"    ⚠ Party '{party.rec_name}' does not have CLIENT category")
        print(f"    Please manually add CLIENT category to this party in Tryton UI")
        return False
    
    # Try to add CLIENT category
    print(f"    Party '{party.rec_name}' does not have CLIENT category - adding it")
    try:
        # Find CLIENT category within the same context
        client_categories = Category.find([('name', '=', 'CLIENT')])
        if not client_categories:
            # Try case-insensitive
            all_categories = Category.find([])
            for cat in all_categories:
                if cat.name.upper() == 'CLIENT':
                    client_categories = [cat]
                    break
        
        if not client_categories:
            print(f"    ✗ Cannot find CLIENT category in the system")
            return False
        
        client_category = client_categories[0]
        
        # Reload the party to work with it
        Party = Model.get('party.party')
        party_to_update = Party(party.id)
        
        # Add CLIENT category
        party_to_update.categories.append(client_category)
        party_to_update.save()
        
        print(f"    ✓ CLIENT category added to party")
        return True
        
    except Exception as e:
        print(f"    ✗ Could not add CLIENT 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 CLIENT category to '{party.rec_name}' in Tryton UI")
        return False


def find_party(party_name, company=None):
    """Find party by name or code"""
    Party = Model.get('party.party')
    
    # Try by name first (since CSV now has party_name)
    parties = Party.find([('name', '=', party_name)])
    if parties:
        party = parties[0]
        return party
    
    # Try by code as fallback
    parties = Party.find([('code', '=', party_name)])
    if parties:
        party = parties[0]
        print(f"    Found party '{party_name}' by code instead of name")
        return party
    
    print(f"  ⚠ Warning: Party with name '{party_name}' 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")
        print(f"    Found location: {location_code} - ID: {locations[0].id}")
        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_code):
    """Find incoterm by code"""
    Incoterm = Model.get('incoterm.incoterm')
    
    if not incoterm_code:
        return None
    
    # Try by incoterm 2025 first
    incoterms = Incoterm.find([('code', '=', incoterm_code, ), ('version', '=', '2025')])
    if incoterms:
        return incoterms[0]
    
    # Try by incoterm 2020 as fallback
    incoterms = Incoterm.find([('code', '=', incoterm_code, ), ('version', '=', '2020')])
    if incoterms:
        return incoterms[0]
    
    print(f"  ⚠ Warning: Incoterm with code '{incoterm_code}' not found")
    return None    


def find_weight_basis(weight_basis_abbr):
    """Find weight basis by abbreviation"""
    WeightBasis = Model.get('purchase.weight.basis')
    
    if not weight_basis_abbr:
        return None
    
    # Try by abbreviation first
    WeightBasisCollection = WeightBasis.find([('name', '=', weight_basis_abbr)])
    if WeightBasisCollection:
        return WeightBasisCollection[0]
    
    
    print(f"  ⚠ Warning: Weight Basis with abbreviation '{weight_basis_abbr}' not found")
    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_sale_exists(number, party):
    """Check if sale with given number and party already exists"""
    if not number:
        return None
    
    conn = get_db_connection()
    if not conn:
        return None
    
    try:
        cursor = conn.cursor()
        cursor.execute("""
            SELECT id FROM sale_sale 
            WHERE number = %s AND party = %s
        """, (number, party.id))
        result = cursor.fetchone()
        cursor.close()
        conn.close()
        
        if result:
            Sale = Model.get('sale.sale')
            return Sale(result[0])
        return None
    except Exception as e:
        print(f"  ⚠ Error checking sale existence: {e}")
        if conn:
            conn.close()
        return None


def update_sale_custom_fields(sale_id, custom_data):
    """Update custom fields in sale 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 sale_sale table
        cursor.execute("""
            SELECT column_name, data_type 
            FROM information_schema.columns 
            WHERE table_name = 'sale_sale'
            AND column_name IN ('number')
            ORDER BY column_name
        """)
        existing_columns = {row[0]: row[1] for row in cursor.fetchall()}
        
        print(f"    Available custom columns in sale_sale:")
        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 'number' in existing_columns and custom_data.get('number'):
            set_clauses.append("number = %s")
            values.append(custom_data['number'])
            print(f"    Adding number = {custom_data['number']}")
        
        if set_clauses:
            values.append(sale_id)
            update_query = f"""
                UPDATE sale_sale 
                SET {', '.join(set_clauses)}
                WHERE id = %s
            """
            
            print(f"    Executing UPDATE with fields: {', '.join([c.split('=')[0].strip() for c in set_clauses])}")
            
            cursor.execute(update_query, values)
            rows_affected = cursor.rowcount
            conn.commit()
            
            if rows_affected > 0:
                print(f"    ✓ {rows_affected} row(s) updated successfully")
            else:
                print(f"    ⚠ No rows updated (sale_id={sale_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 sale 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 sale_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()
        
        # Check if pricing_estimated table exists and what its structure is
        cursor.execute("""
            SELECT column_name 
            FROM information_schema.columns 
            WHERE table_name = 'pricing_estimated'
            ORDER BY ordinal_position
        """)
        
        columns = [row[0] for row in cursor.fetchall()]
        
        if not columns:
            print(f"    Info: pricing_estimated table does not exist, skipping pricing estimate")
            cursor.close()
            conn.close()
            return True
        
        # Insert into pricing_estimated table
        # Note: This assumes the foreign key constraint allows sale_line references
        # If it doesn't, this will fail gracefully
        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()
        print(f"    ✓ Pricing estimate created successfully")
        return True
        
    except psycopg2.errors.ForeignKeyViolation as e:
        # This is expected if pricing_estimated references purchase_line only
        print(f"    Info: Pricing estimate skipped (table references purchase_line only, not sale_line)")
        if conn:
            conn.rollback()
            conn.close()
        return True  # Return True to continue processing
        
    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_sale(row):
    """Create a new sale using proteus"""
    Sale = Model.get('sale.sale')
    
    # Create sale header
    sale = Sale()
    
    # Set company first (required for domain evaluation)
    sale.company = row['company']
    
    # Required fields
    sale.party = row['party']
    sale.currency = row['currency']

    # Optional header fields
    if row.get('reference'):
        sale.reference = row['reference']
   
    # if row.get('number'):
    #     sale.number = row['number']

    if row.get('description'):
        sale.description = row['description']
    
    if row.get('sale_date'):
        sale.sale_date = row['sale_date']
    
    if row.get('warehouse'):
        sale.warehouse = row['warehouse']
    
    if row.get('payment_term'):
        sale.payment_term = row['payment_term']
    
    if row.get('invoice_method'):
        sale.invoice_method = row['invoice_method']
    else:
        sale.invoice_method = DEFAULT_INVOICE_METHOD
    
    if row.get('invoice_address'):
        sale.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:
            sale.invoice_address = invoice_address
    
    if row.get('comment'):
        sale.comment = row['comment']

    if row.get('wb'):
        sale.wb = row['wb']

    if row.get('tol_min'):
        sale.tol_min = row['tol_min']

    if row.get('tol_max'):
        sale.tol_max = row['tol_max']

    if row.get('from_location'):
        sale.from_location = row['from_location']

    if row.get('to_location'):
        sale.to_location = row['to_location']        

    if row.get('incoterm'):
        sale.incoterm = row['incoterm']
    
    # State and status fields
    sale.state = row.get('state', DEFAULT_STATE)
    sale.invoice_state = DEFAULT_INVOICE_STATE
    sale.shipment_state = DEFAULT_SHIPMENT_STATE
    
    # Save sale header first
    sale.save()


     # Update custom fields via SQL if provided (including the number field)
    custom_data = {}
    if row.get('number') is not None:
          custom_data['number'] = row['number']    

    if custom_data:
         update_sale_custom_fields(sale.id, custom_data)
    
    return sale


def create_sale_line(sale, line_data):
    """Create a sale line"""
    Line = Model.get('sale.line')
    
    line = Line()
    line.sale = sale
    line.type = line_data.get('type', 'line')
    line.sequence = 1  # Default sequence, can be adjusted later if needed
    
    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'].sale_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']
        
        # Shipping date
        if line_data.get('shipping_date'):
            line.shipping_date_edit = True
            line.shipping_date_store = line_data['shipping_date']

        # Delivery dates
        if line_data.get('from_del'):
            line.from_del = line_data['from_del']
        if line_data.get('to_del'):
            line.to_del = line_data['to_del']
    
    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_sales(csv_file):
    """Import sales 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 sales 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 CSV columns: {reader.fieldnames}\n")
            
            # Track current sale by number
            sales_by_number = {}
            
            for row_num, row in enumerate(reader, start=2):
                try:
                    # Clean up values
                    number = row.get('number', '').strip()
                    reference = row.get('reference', '').strip()
                    party_name = row.get('party_name', '').strip()
                    
                    # Skip empty rows
                    if not number and not party_name:
                        continue
                    
                    print(f"Processing Row {row_num}: Number '{number}', Reference '{reference}'")
                    
                    # Check if we need to create a new sale for this number
                    if number and number not in sales_by_number:
                        # New sale header
                        
                        # Find required entities
                        party = find_party(party_name, company)
                        if not party:
                            error_msg = f"Row {row_num}: Party '{party_name}' not found"
                            errors.append(error_msg)
                            error_count += 1
                            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_client = any(cat.name.upper() == 'CLIENT' for cat in party.categories)
                            print(f"    Has CLIENT category: {has_client}")
                        else:
                            print(f"    Categories: None")
                        
                        # Ensure party has CLIENT category
                        if not ensure_party_is_client(party, auto_enable=AUTO_ENABLE_CLIENT):
                            if SKIP_NON_CLIENTS:
                                print(f"  ⚠ Skipping sale - party does not have CLIENT category\n")
                                skipped_count += 1
                                continue
                            else:
                                error_msg = f"Row {row_num}: Party '{party.rec_name}' does not have CLIENT category"
                                errors.append(error_msg)
                                error_count += 1
                                continue
                        
                        # Reload party after category addition to get fresh data
                        Party = Model.get('party.party')
                        party = Party(party.id)
                        
                        # Check if sale already exists
                        existing_sale = check_sale_exists(number, party)
                        if existing_sale:
                            print(f"  ⚠ Sale with number '{number}' for party '{party.rec_name}' already exists (ID: {existing_sale.id})")
                            print(f"    Using existing sale...\n")
                            sales_by_number[number] = existing_sale
                            skipped_count += 1
                            # Continue to add line to existing sale
                        else:

                            # 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
                                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
                            sale_date = parse_date(row.get('sale_date', ''))
                            
                            # Find weight basis
                            wb = None
                            weight_basis_abbr = row.get('wb', '').strip()
                            print(f"    Looking for weight basis: '{weight_basis_abbr}'")
                            if weight_basis_abbr:
                                wb = find_weight_basis(weight_basis_abbr)
                                if not wb:
                                    print(f"    Continuing without weight basis")



                            # Parse custom numeric fields
                            #number = parse_decimal(row.get('number', ''), 'number')
                            number = row.get('number', '').strip()

                            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
                            
                            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
                            
                            # Get incoterm by name
                            incoterm = None
                            incoterm_name = row.get('incoterm_name', '').strip()
                            if incoterm_name:
                                incoterm = find_incoterm(incoterm_name)
                            
                            
                            # Prepare sale data
                            sale_data = {
                                'reference': reference,  # Keep reference field
                                'number': number,        # Add number field (stored via SQL)
                                'party': party,
                                'company': company,
                                'currency': currency,
                                'sale_date': sale_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 sale
                            current_sale = create_sale(sale_data)
                            sales_by_number[number] = current_sale
                            
                            print(f"  ✓ Created sale header")
                            print(f"    Sale ID: {current_sale.id}")
                            print(f"    Number: {number}")
                            print(f"    Reference: {reference}")
                            print(f"    Party: {party.rec_name}")
                            print(f"    Currency: {currency.name if hasattr(currency, 'name') else currency.code}")
                            if sale_date:
                                print(f"    Sale Date: {sale_date}")
                            if wb is not None:
                                print(f"    WB: {wb.name}")
                            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"    Loading: {from_location.name}")
                            if to_location:
                                print(f"    Destination: {to_location.name}")
                            if incoterm:
                                print(f"    Incoterm: {incoterm.code}")
                            
                            imported_count += 1
                    
                    # Create sale line if we have a current sale and product data
                    current_sale = sales_by_number.get(number)
                    line_product_code = row.get('line_product_code', '').strip()
                    line_type = row.get('line_type', '').strip() or 'line'
                    
                    if current_sale 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 shipping 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_sale_line(current_sale, 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"      Shipping: {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} - {number if 'number' 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} sales")
        print(f"Skipped (already exist): {skipped_count} sales")
        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 sales"""
    Sale = Model.get('sale.sale')
    
    print(f"\n{'='*70}")
    print("VERIFICATION - Sales")
    print(f"{'='*70}\n")
    
    # Find all sales (or limit to recently created ones)
    sales = Sale.find([], order=[('id', 'DESC')])
    
    if sales:
        print(f"Found {len(sales)} sales (showing last 10):\n")
        print(f"{'ID':<8} {'Number':<15} {'Reference':<15} {'Party':<25} {'State':<12} {'Total':<15}")
        print("-" * 95)
        
        for sale in sales[:10]:  # Show last 10 created
            sale_id = sale.id
            
            # Get number from database since it's a custom field
            conn = get_db_connection()
            number = 'N/A'
            if conn:
                try:
                    cursor = conn.cursor()
                    cursor.execute("SELECT number FROM sale_sale WHERE id = %s", (sale_id,))
                    result = cursor.fetchone()
                    if result and result[0]:
                        number = str(result[0])[:14]
                    cursor.close()
                    conn.close()
                except:
                    if conn:
                        conn.close()
            
            reference = sale.reference[:14] if sale.reference else 'N/A'
            party = sale.party.rec_name[:24] if sale.party else 'N/A'
            state = sale.state if sale.state else 'N/A'
            total = f"{sale.total_amount:.2f}" if sale.total_amount else 'N/A'
            
            print(f"{sale_id:<8} {number:<15} {reference:<15} {party:<25} {state:<12} {total:<15}")
            
            # Show lines
            if sale.lines:
                print(f"  Lines: {len(sale.lines)}")
                for line in sale.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 sales found")
    
    print()


def prepare_parties_as_clients(csv_file):
    """Pre-process: Add CLIENT category to all parties in CSV using Proteus"""
    Party = Model.get('party.party')
    Category = Model.get('party.category')
    
    print(f"{'='*70}")
    print(f"PREPARING PARTIES AS CLIENTS (via Categories)")
    print(f"{'='*70}\n")
    
    # Find CLIENT category
    client_categories = Category.find([('name', '=', 'CLIENT')])
    if not client_categories:
        # Try case-insensitive
        all_categories = Category.find([])
        for cat in all_categories:
            if cat.name.upper() == 'CLIENT':
                client_categories = [cat]
                break
    
    if not client_categories:
        print(f"✗ CLIENT category not found in the system!")
        print(f"Please create a party category named 'CLIENT' first.\n")
        return False
    
    client_category = client_categories[0]
    print(f"Found CLIENT category (ID: {client_category.id})\n")
    
    party_names = set()
    
    try:
        with open(csv_file, 'r', encoding='utf-8-sig') as file:
            reader = csv.DictReader(file)
            for row in reader:
                party_name = row.get('party_name', '').strip()
                if party_name:
                    party_names.add(party_name)
        
        print(f"Found {len(party_names)} unique parties in CSV\n")
        
        updated_count = 0
        already_client_count = 0
        not_found_count = 0
        
        for party_name in party_names:
            print(f"Processing party: {party_name}")
            
            # Find party
            parties = Party.find([('name', '=', party_name)])
            if not parties:
                parties = Party.find([('code', '=', party_name)])
            
            if not parties:
                print(f"  ✗ Not found\n")
                not_found_count += 1
                continue
            
            party = parties[0]
            
            # Check if already has CLIENT category
            has_client = False
            if party.categories:
                for cat in party.categories:
                    if cat.name.upper() == 'CLIENT':
                        has_client = True
                        break
            
            if has_client:
                print(f"  ✓ Already has CLIENT category\n")
                already_client_count += 1
                continue
            
            # Add CLIENT category using Proteus
            try:
                # Reload party and category in same context
                party_to_update = Party(party.id)
                client_cat = Category(client_category.id)
                
                party_to_update.categories.append(client_cat)
                party_to_update.save()
                print(f"  ✓ CLIENT 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 CLIENT category: {already_client_count}")
        print(f"CLIENT 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 SALE 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
    
    # Optional: Prepare all parties as clients first
    # Uncomment the following line to mark all parties in CSV as clients before importing
    # prepare_parties_as_clients(CSV_FILE_PATH)
    
    # Import sales
    import_sales(CSV_FILE_PATH)
    
    # Verify import
    verify_import()
    
    return 0


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