import xmlrpc.client
import psycopg2
import pymysql
import base64
import re
from psycopg2.extras import RealDictCursor
from flask import Flask, render_template, request, session, redirect, url_for, jsonify
from datetime import datetime, timedelta

app = Flask(__name__)
# Secret key for sessions
app.secret_key = 'super_secret_tada_migration_key_change_me_in_prod'
app.permanent_session_lifetime = timedelta(hours=8)

# --- Configurations ---
# PostgreSQL (Hardcoded as requested)
DB_HOST = "192.169.0.10"
DB_PORT = "5432"
DB_NAME = "postgres"
DB_USER = "postgres"
DB_PASS = "Mulut!23Berkomunikasi"

# Odoo 19
ODOO_URL = "http://localhost:1900"
ODOO_DB = "odoo19_OT_v3"
ODOO_USER = "admin"
ODOO_PASS = "admin"

# MySQL (Login DB)
MYSQL_HOST = "192.169.0.13"
MYSQL_PORT = 3306
MYSQL_DB = "klaim_db"
MYSQL_USER = "u8975009_root"
MYSQL_PASS = "Jempol&1992Mutu_hosting"

def get_mysql_connection():
    return pymysql.connect(
        host=MYSQL_HOST,
        port=MYSQL_PORT,
        user=MYSQL_USER,
        password=MYSQL_PASS,
        database=MYSQL_DB,
        cursorclass=pymysql.cursors.DictCursor
    )

def normalize_phone(phone):
    if not phone:
        return ""
    # Remove all non-digits
    num = re.sub(r'\D', '', str(phone))
    if num.startswith('62'):
        num = num[2:]
    elif num.startswith('0'):
        num = num[1:]
    return num

def get_db_connection():
    conn = psycopg2.connect(
        host=DB_HOST,
        port=DB_PORT,
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASS
    )
    return conn

@app.route('/')
def index():
    if 'employee_id' not in session:
        return redirect(url_for('login'))
    return render_template('index.html', employee_name=session.get('employee_name', session['employee_id']))

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        employee_id = request.form.get('employee_id')
        password = request.form.get('password')
        
        if employee_id and password:
            try:
                conn = get_mysql_connection()
                with conn.cursor() as cursor:
                    cursor.execute("SELECT EMP_FULL_NAME, EMP_PASS FROM m_employee WHERE EMP_F_NUM = %s", (employee_id,))
                    user = cursor.fetchone()
                    
                    if user:
                        db_pass_encoded = user['EMP_PASS']
                        
                        # Check default unencrypted password first
                        if password == "11111" and db_pass_encoded == "11111":
                            is_valid = True
                        else:
                            input_pass_encoded = base64.b64encode(password.encode('utf-8')).decode('utf-8')
                            is_valid = (input_pass_encoded == db_pass_encoded)
                        
                        if is_valid:
                            session.permanent = True
                            session['employee_id'] = employee_id
                            session['employee_name'] = user['EMP_FULL_NAME']
                            return redirect(url_for('index'))
                        else:
                            error = "Invalid password."
                    else:
                        error = "Employee ID not found."
            except Exception as e:
                error = f"Database error: {str(e)}"
            finally:
                if 'conn' in locals() and conn.open:
                    conn.close()
        else:
            error = "Please provide both Employee ID and Password."
            
    return render_template('login.html', error=error)

@app.route('/logout')
def logout():
    session.pop('employee_id', None)
    session.pop('employee_name', None)
    return redirect(url_for('login'))

@app.route('/api/customers')
def get_customers():
    if 'employee_id' not in session:
        return jsonify({"error": "Unauthorized"}), 401
    
    # DataTables parameters
    draw = request.args.get('draw', type=int, default=1)
    start = request.args.get('start', type=int, default=0)
    length = request.args.get('length', type=int, default=10)
    search_value = request.args.get('search[value]', default="")
    
    order_column_index = request.args.get('order[0][column]', type=int, default=0)
    order_dir = request.args.get('order[0][dir]', default='asc')
    
    columns_map = ['id', 'name', 'phone_number', 'email', 'level', 'point_amount', 'responsible']
    order_column = columns_map[order_column_index] if order_column_index < len(columns_map) else 'id'
    if order_dir not in ['asc', 'desc']:
        order_dir = 'asc'
    
    conn = None
    try:
        conn = get_db_connection()
        cursor = conn.cursor(cursor_factory=RealDictCursor)
        
        # Base query
        query = "SELECT * FROM tada_member"
        count_query = "SELECT COUNT(*) FROM tada_member"
        params = []
        
        if search_value:
            search_pattern = f"%{search_value}%"
            where_clause = " WHERE name ILIKE %s OR phone_number ILIKE %s OR email ILIKE %s"
            query += where_clause
            count_query += where_clause
            params.extend([search_pattern, search_pattern, search_pattern])
            
        # Get total filtered count
        cursor.execute(count_query, params)
        total_filtered = cursor.fetchone()['count']
        
        # Total records without filter
        cursor.execute("SELECT COUNT(*) FROM tada_member")
        total_records = cursor.fetchone()['count']
        
        # Add pagination and sorting
        query += f" ORDER BY {order_column} {order_dir.upper()} LIMIT %s OFFSET %s"
        params.extend([length, start])
        
        cursor.execute(query, params)
        customers = cursor.fetchall()
        
        # Convert date fields to string if any to allow JSON serialization
        for row in customers:
            if row.get('birthday'):
                row['birthday'] = str(row['birthday'])
            if row.get('updated_at'):
                row['updated_at'] = str(row['updated_at'])
                
        return jsonify({
            "draw": draw,
            "recordsTotal": total_records,
            "recordsFiltered": total_filtered,
            "data": customers
        })
    except Exception as e:
        print(f"Error fetching customers: {e}")
        return jsonify({"error": str(e)}), 500
    finally:
        if conn:
            conn.close()

@app.route('/api/migrate/<int:customer_id>', methods=['POST'])
def migrate_customer(customer_id):
    if 'employee_id' not in session:
        return jsonify({"success": False, "message": "Unauthorized"}), 401
        
    employee_id = session['employee_id']
    conn = None
    try:
        # Fetch customer data
        conn = get_db_connection()
        cursor = conn.cursor(cursor_factory=RealDictCursor)
        cursor.execute("SELECT * FROM tada_member WHERE id = %s", (customer_id,))
        customer = cursor.fetchone()
        
        if not customer:
            return jsonify({"success": False, "message": "Customer not found."}), 404
            
        # Odoo connection
        common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
        uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASS, {})
        
        if not uid:
            return jsonify({"success": False, "message": "Failed to authenticate with Odoo."}), 500
            
        models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')
        
        # 1. Normalize phone and check if partner exists
        phone_raw = customer.get('phone_number') or ''
        norm_phone = normalize_phone(phone_raw)
        
        partner_id = False
        existing_partner = None
        
        if norm_phone:
            partner_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'res.partner', 'search_read', 
                [[('phone', 'ilike', norm_phone)]],
                {'fields': ['name', 'phone', 'email', 'gender', 'birth_date', 'city', 'total_spend', 'membership_level_id'], 'limit': 1}
            )
            if partner_ids:
                existing_partner = partner_ids[0]
                partner_id = existing_partner['id']
                
        # 2. Find loyalty.program based on level
        level = customer.get('level', '')
        level_map = {
            'Silver': 'Membership Silver',
            'Gold': 'Membership Gold',
            'Platinum': 'Membership Platinum'
        }
        target_program_name = level_map.get(level, f"Membership {level}")
        
        # 1. Search by exact mapped name
        program_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.program', 'search', [[('name', '=', target_program_name)]])
        if not program_ids:
            # 2. Search by exact level name
            program_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.program', 'search', [[('name', '=', level)]])
        if not program_ids:
            # 3. Fallback to ilike
            program_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.program', 'search', [[('name', 'ilike', level)]])
        
        if not program_ids:
            return jsonify({"success": False, "message": f"Loyalty program containing '{level}' not found in Odoo."}), 404
            
        program_id = program_ids[0]
        
        # Map gender safely (e.g. 'female' -> 'Female')
        gender_val = customer.get('gender')
        if isinstance(gender_val, str) and gender_val:
            gender_val = gender_val.title()
            
        pg_total_spend = float(customer.get('total_spending') or 0.0)
        
        if existing_partner:
            # Update existing partner (fill empty fields)
            update_data = {}
            if not existing_partner.get('email') and customer.get('email'):
                update_data['email'] = customer.get('email')
            if not existing_partner.get('gender') and gender_val:
                update_data['gender'] = gender_val
            if not existing_partner.get('birth_date') and customer.get('birthday'):
                update_data['birth_date'] = str(customer.get('birthday'))
            if not existing_partner.get('city') and customer.get('city'):
                update_data['city'] = customer.get('city')
            if not existing_partner.get('membership_level_id'):
                update_data['membership_level_id'] = program_id
                
            # Accumulate total_spend
            current_spend = float(existing_partner.get('total_spend') or 0.0)
            if pg_total_spend > 0:
                update_data['total_spend'] = current_spend + pg_total_spend
                
            if update_data:
                models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'res.partner', 'write', [[partner_id], update_data])
        else:
            # Create new partner
            partner_data = {
                'name': customer.get('name', ''),
                'phone': phone_raw,
                'email': customer.get('email', ''),
                'gender': gender_val or '',
                'birth_date': str(customer.get('birthday', '')) if customer.get('birthday') else False,
                'city': customer.get('city', ''),
                'membership_level_id': program_id,
                'total_spend': pg_total_spend
            }
            # Replace None with False for Odoo XML-RPC compatibility
            partner_data = {k: (v if v is not None else False) for k, v in partner_data.items()}
            partner_id = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'res.partner', 'create', [partner_data])
            
        # 3. Handle loyalty.card
        point_amount = float(customer.get('point_amount') or 0.0)
        
        card_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.card', 'search_read',
            [[('partner_id', '=', partner_id), ('program_id', '=', program_id)]],
            {'fields': ['points'], 'limit': 1}
        )
        
        if card_ids:
            # Accumulate points
            existing_card = card_ids[0]
            current_points = float(existing_card.get('points') or 0.0)
            if point_amount > 0:
                models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.card', 'write', [[existing_card['id']], {'points': current_points + point_amount}])
        else:
            # Create new card
            card_data = {
                'partner_id': partner_id,
                'program_id': program_id,
                'points': point_amount
            }
            models.execute_kw(ODOO_DB, uid, ODOO_PASS, 'loyalty.card', 'create', [card_data])
        
        # 4. Update tada_member
        now = datetime.now()
        cursor.execute("UPDATE tada_member SET responsible = %s, updated_at = %s WHERE id = %s", (employee_id, now, customer_id))
        conn.commit()
        
        return jsonify({"success": True, "message": "Migrasi data berhasil!"})
        
    except Exception as e:
        if conn:
            conn.rollback()
        print(f"Migration Error: {e}")
        return jsonify({"success": False, "message": str(e)}), 500
    finally:
        if conn:
            conn.close()

if __name__ == '__main__':
    app.run(debug=True, port=5000, host='0.0.0.0')
