#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Scripts de migración de datos del sistema Laravel de SKEEN a Odoo 17.

Uso:
  1. Exportar CSVs del sistema Laravel (pacientes, servicios, productos, médicos, etc.)
     o generar CSVs con el formato esperado.
  2. Colocar los archivos en /root/migracion/datos/
  3. Ejecutar: source /root/odoo-venv/bin/activate && python3 /root/migracion/importar_a_odoo.py

Estructura esperada de CSVs:
  - pacientes.csv: nombre, apellido_paterno, apellido_materno, telefono, email, fecha_nacimiento, sexo, medico_principal, etiquetas, vip, fuente
  - servicios.csv: codigo, nombre, categoria, precio, duracion_min, descripcion, color, grupo, favorito, activo
  - productos.csv: codigo, nombre, categoria, descripcion, precio, inventario_minimo, activo
  - medicos.csv: nombre, apellido_paterno, apellido_materno, telefono, email, especialidad, rol
  - usuarios.csv: nombre, login, email, password, rol, activo
  - citas.csv: paciente_telefono, paciente_nombre, servicio_codigo, fecha, hora, estado, doctor_nombre, sucursal, medio, notas, estado_pago, monto_pagado
  - ventas.csv: paciente_telefono, fecha, servicio_codigo, cantidad, precio_unitario, descuento, impuesto, estado, notas
  - pagos.csv: paciente_telefono, cita_referencia, monto, metodo, estado, fecha_pago
  - adeudos.csv: paciente_telefono, concepto, monto, moneda, fecha_vencimiento
  - monederos.csv: telefono, puntos, activo

El script es idempotente: busca por teléfono / código y actualiza si ya existe.
"""

import csv
import os
import re
import sys
import argparse
from pathlib import Path

# Odoo bootstrap
ODOO_PATH = Path('/root/skeen-odoo')
sys.path.insert(0, str(ODOO_PATH))

import odoo
from odoo import api, fields, SUPERUSER_ID

DATA_DIR = Path('/root/migracion/datos')


def _normalize_name(name):
    return ' '.join(str(name).split())


def get_or_create_doctor(env, doctor_name):
    """Busca o crea un empleado (médico/especialista)."""
    name = _normalize_name(doctor_name or '')
    if not name:
        return False
    # Normalizar títulos para búsqueda
    clean = name.lower().replace('dra.', '').replace('dr.', '').replace('dr ', '').replace('dra ', '').strip()
    doctor = env['hr.employee'].sudo().search([
        '|', ('name', 'ilike', name), ('name', 'ilike', clean)
    ], limit=1)
    if not doctor:
        doctor = env['hr.employee'].sudo().create({'name': name})
    return doctor


def get_or_create_service(env, service_code='', service_name=''):
    """Busca servicio por código o nombre; si no existe crea uno genérico."""
    service_code = (service_code or '').strip()
    service_name = _normalize_name(service_name or '')
    servicio = False
    if service_code:
        servicio = env['skeen.servicio'].sudo().with_context(active_test=False).search([('code', '=', service_code)], limit=1)
    if not servicio and service_name:
        servicio = env['skeen.servicio'].sudo().with_context(active_test=False).search([('name', 'ilike', service_name)], limit=1)
    if not servicio and service_name:
        # Crear servicio genérico basado en el nombre histórico
        import hashlib
        name_hash = hashlib.md5(service_name.encode('utf-8')).hexdigest()[:12]
        code = f'HIST_{name_hash}'
        servicio = env['skeen.servicio'].sudo().with_context(active_test=False).search([('code', '=', code)], limit=1)
        if not servicio:
            servicio = env['skeen.servicio'].sudo().create({
                'name': service_name,
                'code': code,
                'category': 'tratamiento',
                'price': 0,
                'duration_min': 30,
            })
    if not servicio:
        servicio = env['skeen.servicio'].sudo().with_context(active_test=False).search([('code', '=', 'GENERICO')], limit=1)
        if not servicio:
            servicio = env['skeen.servicio'].sudo().create({
                'name': 'Servicio genérico',
                'code': 'GENERICO',
                'category': 'tratamiento',
                'price': 0,
                'duration_min': 30,
            })
    return servicio


def get_or_create_patient(env, phone, name, **vals):
    """Busca o crea un paciente por teléfono."""
    partner = env['res.partner'].sudo().search([('phone', '=', phone)], limit=1)
    if partner:
        return partner
    create_vals = {
        'name': name,
        'phone': phone,
        'is_patient': True,
    }
    create_vals.update(vals)
    return env['res.partner'].sudo().create(create_vals)


def importar_pacientes(env, filepath):
    """Importa pacientes desde CSV."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            name = ' '.join(filter(None, [
                row.get('nombre', ''),
                row.get('apellido_paterno', ''),
                row.get('apellido_materno', '')
            ])).strip()
            phone = (row.get('telefono') or '').strip()
            if not name or not phone:
                continue

            vals = {
                'email': row.get('email', ''),
            }
            source = (row.get('fuente') or 'other').strip().lower()
            source_map = {
                'whatsapp': 'whatsapp',
                'facebook': 'facebook',
                'instagram': 'instagram',
                'google': 'google',
                'referral': 'referral',
                'walkin': 'walkin',
                'other': 'other',
            }
            vals['source'] = source_map.get(source, 'other')
            if row.get('vip', '').lower() in ('1', 'true', 'si', 'sí'):
                vals['is_vip'] = True
            if row.get('fecha_nacimiento'):
                vals['birth_date'] = row.get('fecha_nacimiento')
            if row.get('sexo'):
                vals['gender'] = row.get('sexo')

            medico_nombre = row.get('medico_principal', '').strip()
            if medico_nombre:
                medico = get_or_create_doctor(env, medico_nombre)
                if medico:
                    vals['primary_doctor_id'] = medico.id

            etiquetas = row.get('etiquetas', '').strip()
            if etiquetas:
                tag_names = [t.strip() for t in etiquetas.split(',') if t.strip()]
                tags = env['skeen.patient.tag'].sudo()
                tag_ids = []
                for tag_name in tag_names:
                    tag = tags.search([('name', '=', tag_name)], limit=1)
                    if not tag:
                        tag = tags.create({'name': tag_name})
                    tag_ids.append(tag.id)
                vals['tag_ids'] = [(6, 0, tag_ids)]

            get_or_create_patient(env, phone, name, **vals)
            count += 1
            if count % 500 == 0:
                env.cr.commit()
                print(f'  pacientes: {count} procesados')
        env.cr.commit()
        return count


def importar_servicios(env, filepath):
    """Importa catálogo de servicios."""
    category_map = {
        'consulta': 'consulta',
        'tratamiento': 'tratamiento',
        'procedimiento': 'procedimiento',
        'paquete': 'paquete',
        'servicios_medicos': 'consulta',
        'procedimientos_cosmeticos': 'procedimiento',
        'tecnologia_medica': 'tratamiento',
        'cosmetologia_basica': 'tratamiento',
        'cosmetologia_avanzada': 'tratamiento',
    }
    seen = set()
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            code = (row.get('codigo') or '').strip()
            name = (row.get('nombre') or '').strip()
            if not code or not name or code in seen:
                continue
            seen.add(code)
            existing = env['skeen.servicio'].sudo().with_context(active_test=False).search([('code', '=', code)], limit=1)
            if existing:
                continue
            cat = category_map.get((row.get('categoria') or '').strip().lower(), 'tratamiento')
            env['skeen.servicio'].sudo().create({
                'name': name,
                'code': code,
                'category': cat,
                'price': float(row.get('precio', 0) or 0),
                'duration_min': int(row.get('duracion_min', 30) or 30),
                'description': row.get('descripcion', ''),
                'active': row.get('activo', '1').lower() in ('1', 'true', 'si'),
            })
            count += 1
            if count % 100 == 0:
                env.cr.commit()
                print(f'  servicios: {count} procesados')
        env.cr.commit()
        return count


def importar_productos(env, filepath):
    """Importa productos / inventario."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            name = (row.get('nombre') or '').strip()
            if not name:
                continue
            # Crear como product.template básico de Odoo + campos custom si aplica
            existing = env['product.template'].sudo().search([('name', '=', name)], limit=1)
            if existing:
                continue
            env['product.template'].sudo().create({
                'name': name,
                'list_price': float(row.get('precio', 0) or 0),
                'type': 'product',
                'default_code': row.get('codigo', ''),
                'description_sale': row.get('descripcion', ''),
            })
            count += 1
        return count


def importar_medicos(env, filepath):
    """Importa médicos/cosmetólogos como empleados."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            name = ' '.join(filter(None, [
                row.get('nombre', ''),
                row.get('apellido_paterno', ''),
                row.get('apellido_materno', '')
            ])).strip()
            if not name:
                continue
            existing = env['hr.employee'].sudo().search([('name', '=', name)], limit=1)
            if existing:
                continue
            env['hr.employee'].sudo().create({
                'name': name,
                'work_phone': row.get('telefono', ''),
                'work_email': row.get('email', ''),
                'job_title': row.get('especialidad', ''),
            })
            count += 1
        return count


def importar_monederos(env, filepath):
    """Importa saldos de monedero por teléfono de paciente."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            phone = (row.get('telefono') or '').strip()
            points = int(float(row.get('puntos', 0) or 0))
            if not phone:
                continue
            partner = env['res.partner'].sudo().search([('phone', '=', phone)], limit=1)
            if not partner:
                partner = get_or_create_patient(env, phone, 'Paciente ' + phone)
            monedero = env['skeen.monedero'].sudo().search([
                ('partner_id', '=', partner.id)
            ], limit=1)
            if monedero:
                monedero.write({'points': points})
            else:
                env['skeen.monedero'].sudo().create({
                    'partner_id': partner.id,
                    'points': points,
                })
            count += 1
        return count


def _parse_time(value):
    """Convierte '09:30' o '9.5' a float de horas."""
    if not value:
        return 9.0
    value = str(value).strip()
    if ':' in value:
        h, m = value.split(':')[:2]
        return float(h) + float(m) / 60.0
    try:
        return float(value)
    except ValueError:
        return 9.0


def importar_citas(env, filepath):
    """Importa citas históricas y futuras."""
    # Desactivar temporalmente la validación de solapamiento para la migración masiva
    CitaModel = env.registry['skeen.cita']
    original_constraint = CitaModel._check_availability
    CitaModel._check_availability = lambda self: None

    try:
        with open(filepath, newline='', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            count = 0
            for row in reader:
                phone = (row.get('paciente_telefono') or '').strip()
                name = (row.get('paciente_nombre') or '').strip()
                date_str = (row.get('fecha') or '').strip()
                time_str = (row.get('hora') or '').strip()
                if not phone or not date_str:
                    continue

                partner = env['res.partner'].sudo().search([('phone', '=', phone)], limit=1)
                if not partner:
                    partner = get_or_create_patient(env, phone, name or 'Paciente ' + phone)

                servicio = get_or_create_service(
                    env,
                    row.get('servicio_codigo', ''),
                    row.get('servicio_nombre', '')
                )

                state_map = {
                    'pendiente': 'pending',
                    'confirmada': 'confirmed',
                    'llego': 'arrived',
                    'en_progreso': 'in_progress',
                    'completada': 'done',
                    'cancelada': 'cancelled',
                    'no_show': 'no_show',
                }
                state = state_map.get((row.get('estado') or '').strip().lower(), 'confirmed')

                payment_state_map = {
                    'no_pagado': 'not_paid',
                    'parcial': 'partial',
                    'pagado': 'paid',
                }
                payment_state = payment_state_map.get((row.get('estado_pago') or '').strip().lower(), 'not_paid')

                doctor = get_or_create_doctor(env, row.get('doctor_nombre', ''))

                branch = 'rosarito'
                if (row.get('sucursal') or '').strip().lower() in ('tijuana', 'tj'):
                    branch = 'tijuana'

                medium = 'onsite'
                if (row.get('medio') or '').strip().lower() in ('videollamada', 'video', 'remota'):
                    medium = 'videocall'

                vals = {
                    'partner_id': partner.id,
                    'servicio_id': servicio.id,
                    'date': date_str,
                    'time': _parse_time(time_str),
                    'state': state,
                    'payment_state': payment_state,
                    'amount_paid': float(row.get('monto_pagado', 0) or 0),
                    'doctor_id': doctor.id if doctor else False,
                    'branch': branch,
                    'medium': medium,
                    'notes': row.get('notas', ''),
                }
                env['skeen.cita'].sudo().create(vals)
                count += 1
                if count % 500 == 0:
                    env.cr.commit()
                    print(f'  citas: {count} procesadas')
            env.cr.commit()
            return count
    finally:
        CitaModel._check_availability = original_constraint


def importar_ventas(env, filepath):
    """Importa ventas históricas."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            phone = (row.get('paciente_telefono') or '').strip()
            if not phone:
                continue

            partner = env['res.partner'].sudo().search([('phone', '=', phone)], limit=1)
            if not partner:
                partner = get_or_create_patient(env, phone, 'Paciente ' + phone)

            servicio = get_or_create_service(
                env,
                row.get('servicio_codigo', ''),
                row.get('servicio_nombre', '')
            )

            qty = float(row.get('cantidad', 1) or 1)
            price = float(row.get('precio_unitario', servicio.price) or servicio.price)
            discount = float(row.get('descuento', 0) or 0)
            tax = float(row.get('impuesto', 0) or 0)

            state_map = {
                'borrador': 'draft',
                'confirmada': 'confirmed',
                'pagada': 'paid',
                'parcial': 'partial',
                'cancelada': 'cancelled',
            }
            state = state_map.get((row.get('estado') or '').strip().lower(), 'confirmed')

            venta = env['skeen.venta'].sudo().create({
                'partner_id': partner.id,
                'date': row.get('fecha') or fields.Date.today(),
                'discount': discount,
                'tax': tax,
                'state': state,
                'notes': row.get('notas', ''),
                'line_ids': [(0, 0, {
                    'service_id': servicio.id,
                    'description': servicio.name,
                    'quantity': qty,
                    'price_unit': price,
                })],
            })

            # Si está pagada, marcar como pagada para que acumule puntos
            if state == 'paid':
                venta.action_mark_paid()

            count += 1
            if count % 500 == 0:
                env.cr.commit()
                print(f'  ventas: {count} procesadas')
        env.cr.commit()
        return count


def importar_pagos(env, filepath):
    """Importa pagos históricos vinculados a pacientes y citas."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            phone = (row.get('paciente_telefono') or '').strip()
            amount = float(row.get('monto', 0) or 0)
            if not phone or amount <= 0:
                continue

            partner = env['res.partner'].sudo().search([('phone', '=', phone)], limit=1)
            if not partner:
                partner = get_or_create_patient(env, phone, 'Paciente ' + phone)

            cita_ref = (row.get('cita_referencia') or '').strip()
            cita_id = False
            if cita_ref:
                cita = env['skeen.cita'].sudo().search([('name', '=', cita_ref)], limit=1)
                cita_id = cita.id if cita else False

            method_map = {
                'efectivo': 'cash',
                'tarjeta': 'card',
                'transferencia': 'transfer',
                'stripe': 'stripe',
                'mercadopago': 'mercadopago',
            }
            method = method_map.get((row.get('metodo') or '').strip().lower(), 'cash')

            state_map = {
                'pendiente': 'pending',
                'procesando': 'processing',
                'completado': 'completed',
                'fallido': 'failed',
                'reembolsado': 'refunded',
            }
            state = state_map.get((row.get('estado') or '').strip().lower(), 'completed')

            vals = {
                'partner_id': partner.id,
                'cita_id': cita_id,
                'amount': amount,
                'payment_method': method,
                'state': state,
            }
            if row.get('fecha_pago'):
                vals['payment_date'] = row.get('fecha_pago')

            pago = env['skeen.pago'].sudo().create(vals)
            if state == 'completed':
                pago.action_confirm_payment()
            count += 1
        return count


def importar_adeudos(env, filepath):
    """Importa adeudos como ventas en estado draft/con adeudo."""
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        count = 0
        for row in reader:
            phone = (row.get('telefono') or '').strip()
            concepto = (row.get('concepto') or 'Adeudo').strip()
            monto = float(row.get('monto', 0) or 0)
            if not phone or monto <= 0:
                continue

            partner = env['res.partner'].sudo().search([('phone', '=', phone)], limit=1)
            if not partner:
                partner = get_or_create_patient(env, phone, 'Paciente ' + phone)

            # Crear un servicio genérico de adeudo si no existe
            servicio = env['skeen.servicio'].sudo().with_context(active_test=False).search([('code', '=', 'ADEUDO')], limit=1)
            if not servicio:
                servicio = env['skeen.servicio'].sudo().create({
                    'name': 'Adeudo histórico',
                    'code': 'ADEUDO',
                    'category': 'consulta',
                    'price': 0,
                    'duration_min': 0,
                })

            env['skeen.venta'].sudo().create({
                'partner_id': partner.id,
                'date': row.get('fecha_vencimiento') or fields.Date.today(),
                'state': 'confirmed',
                'notes': concepto,
                'line_ids': [(0, 0, {
                    'service_id': servicio.id,
                    'description': concepto,
                    'quantity': 1,
                    'price_unit': monto,
                })],
            })
            count += 1
        return count


def main():
    parser = argparse.ArgumentParser(description='Importar datos de SKEEN a Odoo')
    parser.add_argument('--odoo-config', default='/root/skeen-odoo/odoo.conf')
    parser.add_argument('--database', default='skeen_odoo')
    args = parser.parse_args()

    odoo.tools.config.parse_config(['-c', args.odoo_config])
    db = odoo.sql_db.db_connect(args.database)
    with db.cursor() as cr:
        env = api.Environment(cr, SUPERUSER_ID, {})

        resultados = {}

        pacientes_path = DATA_DIR / 'pacientes.csv'
        if pacientes_path.exists():
            resultados['pacientes'] = importar_pacientes(env, pacientes_path)

        servicios_path = DATA_DIR / 'servicios.csv'
        if servicios_path.exists():
            resultados['servicios'] = importar_servicios(env, servicios_path)

        productos_path = DATA_DIR / 'productos.csv'
        if productos_path.exists():
            resultados['productos'] = importar_productos(env, productos_path)

        medicos_path = DATA_DIR / 'medicos.csv'
        if medicos_path.exists():
            resultados['medicos'] = importar_medicos(env, medicos_path)

        citas_path = DATA_DIR / 'citas.csv'
        if citas_path.exists():
            resultados['citas'] = importar_citas(env, citas_path)

        ventas_path = DATA_DIR / 'ventas.csv'
        if ventas_path.exists():
            resultados['ventas'] = importar_ventas(env, ventas_path)

        pagos_path = DATA_DIR / 'pagos.csv'
        if pagos_path.exists():
            resultados['pagos'] = importar_pagos(env, pagos_path)

        adeudos_path = DATA_DIR / 'adeudos.csv'
        if adeudos_path.exists():
            resultados['adeudos'] = importar_adeudos(env, adeudos_path)

        monederos_path = DATA_DIR / 'monederos.csv'
        if monederos_path.exists():
            resultados['monederos'] = importar_monederos(env, monederos_path)

        cr.commit()

        print('Importación completada:')
        for k, v in resultados.items():
            print(f'  {k}: {v} registros')


if __name__ == '__main__':
    main()
