#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Delta import SKEEN: trae citas y pacientes nuevos del legacy desde la última
migración hasta hoy (+60 días de futuras), SIN duplicar.

- Legacy: SOLO LECTURA (reusa login/fetch de extraer_skeen.py).
- Citas: dedup por terna (partner_id, date, time). Existentes: actualiza state
  solo con información que el legacy sí expone (ver STATUS_MAP). El endpoint de
  agenda NO expone pagos, así que payment_state/amount_paid no se tocan.
- Pacientes: crea solo legacy_id nuevo Y teléfono nuevo. Sin detalle clínico
  nuevo; usa caché detalle_pacientes.json para email/fecha_nac/sexo si existe.

Uso: /root/odoo-venv/bin/python /root/migracion/delta_import.py
"""
import csv
import html
import json
import re
import sys
import time
import hashlib
from datetime import datetime, timedelta
from pathlib import Path

sys.path.insert(0, '/root/migracion')
sys.path.insert(0, '/root/skeen-odoo')

import extraer_skeen as ex  # login(), fetch_json(), paginate(), dt_params(), normalize_phone()

import odoo
from odoo import api, SUPERUSER_ID

START = '2026-06-01'
HOY = datetime.now().date()
END = HOY + timedelta(days=60)
STAMP = datetime.now().strftime('%Y%m%d_%H%M%S')
OUTDIR = Path('/root/migracion/extraccion')

# Leyenda de status del legacy (radios name="status" en /agenda):
# 0 Normal, 1 No vino, 2 Canceló/Reagendó, 3 Canceló, 4/5 Lista de espera,
# 6 No citar, 7 Vacaciones, 8 Horario de comida
EXCLUDE_STATUS = {6, 7, 8}


def map_state(status, confirmada, tiene_visita):
    """Estado Odoo según status legacy. None = no importar (bloques de agenda)."""
    if status in EXCLUDE_STATUS:
        return None
    if status == 1:
        return 'no_show'
    if status in (2, 3):
        return 'cancelled'
    if tiene_visita:
        return 'done'
    return 'confirmed' if confirmada else 'pending'


# Solo avanzar estados: nunca regresar done/arrived/etc. a confirmed/pending
RANK = {'pending': 0, 'confirmed': 1, 'arrived': 2, 'in_progress': 3,
        'done': 4, 'cancelled': 5, 'no_show': 5}


def should_update(actual, nuevo):
    if nuevo == actual:
        return False
    if nuevo in ('cancelled', 'no_show'):
        return actual not in ('cancelled', 'no_show')
    return RANK.get(nuevo, 0) > RANK.get(actual, 0)


def normalize_name(name):
    return ' '.join(str(name or '').split()).lower()


def parse_time_hhmm(inicio):
    inicio = (inicio or '').strip()
    if len(inicio) == 4:
        return float(inicio[:2]) + float(inicio[2:]) / 60.0
    if len(inicio) == 3:
        return float(inicio[0]) + float(inicio[1:]) / 60.0
    return 9.0


def extract_service(text, patient_name):
    text = html.unescape(text or '')
    if patient_name:
        text = re.sub(r'^' + re.escape(patient_name) + r'\s*[-–—]\s*', '', text, flags=re.IGNORECASE)
    text = re.sub(r'\s*\(\d+\s*(sesión|sesiones|unidad|unidades)\s*\)\s*$', '', text, flags=re.IGNORECASE)
    return ' '.join(text.split()).strip()


def log(msg):
    print(f'[{datetime.now().strftime("%H:%M:%S")}] {msg}', flush=True)


def main():
    # ---------- Conexión Odoo ----------
    odoo.tools.config.parse_config(['-c', '/root/skeen-odoo/odoo.conf'])
    db = odoo.sql_db.db_connect('skeen_odoo')
    cr = db.cursor()
    env = api.Environment(cr, SUPERUSER_ID, {})
    Partner = env['res.partner'].sudo()
    Servicio = env['skeen.servicio'].sudo()
    CitaModel = env.registry['skeen.cita']
    Cita = env['skeen.cita'].sudo()

    # Desactivar validación de solapamiento durante la importación
    original_check = CitaModel._check_availability
    CitaModel._check_availability = lambda self: None

    # ---------- Cachés Odoo ----------
    log('Cargando cachés de Odoo...')
    partner_by_legacy = {}
    partner_by_phone = {}
    cr.execute("SELECT id, phone, legacy_id FROM res_partner WHERE is_patient = true AND active = true")
    for pid, phone, legacy_id in cr.fetchall():
        if legacy_id:
            partner_by_legacy[str(legacy_id)] = pid
        if phone:
            partner_by_phone.setdefault(phone, pid)

    service_by_name = {}
    cr.execute("SELECT id, name FROM skeen_servicio")
    for sid, name in cr.fetchall():
        if name:
            service_by_name.setdefault(normalize_name(name), sid)

    doctor_by_name = {}
    cr.execute("SELECT id, name FROM hr_employee")
    for eid, name in cr.fetchall():
        if name:
            doctor_by_name.setdefault(normalize_name(name), eid)

    # Citas existentes en el rango (dedup + posibles actualizaciones)
    existing_citas = {}
    cr.execute(
        "SELECT id, partner_id, date, time, state FROM skeen_cita WHERE date >= %s AND date <= %s",
        (START, END.strftime('%Y-%m-%d')))
    for cid, pid, d, t, state in cr.fetchall():
        existing_citas[(pid, d.strftime('%Y-%m-%d'), round(float(t), 2))] = (cid, state)
    log(f'  Pacientes: {len(partner_by_legacy)} legacy / {len(partner_by_phone)} teléfonos | '
        f'Servicios: {len(service_by_name)} | Citas en rango: {len(existing_citas)}')

    medicos_map = {
        '32': 'Dra. Alejandra Ramos', '33': 'Dra. Lidia Martinez',
        '46': 'Dra. Fernanda Cerecer', '49': 'RODRIGUEZ FRIDA',
        '54': 'Dr. Benjamín Adrián', '79': 'XIMENA', '80': 'ELY',
    }

    def get_or_create_service(name):
        name = name.strip() or 'Servicio genérico'
        key = normalize_name(name)
        if key in service_by_name:
            return service_by_name[key]
        code = 'HIST_' + hashlib.md5(key.encode('utf-8')).hexdigest()[:12]
        new_s = Servicio.create({'name': name, 'code': code, 'category': 'tratamiento',
                                 'price': 0, 'duration_min': 30})
        service_by_name[key] = new_s.id
        return new_s.id

    # ---------- Login legacy ----------
    ex.login()

    # ---------- 1. Pacientes nuevos ----------
    log('Descargando expedientes del legacy...')
    exp_rows = ex.paginate('/expedientes/json', lambda s, l: ex.dt_params(s, l), 'expedientes')
    detail_cache = ex.load_detail_cache()

    pacientes_nuevos = 0
    omitidos_legacy = 0
    omitidos_phone = 0
    omitidos_invalid = 0
    delta_pacientes = []
    for r in exp_rows:
        legacy_id = str(r.get('id') or '').strip()
        nombre = (r.get('nombre') or '').strip()
        if not legacy_id or not nombre or nombre.upper().startswith('. . NO CITAR'):
            omitidos_invalid += 1
            continue
        if legacy_id in partner_by_legacy:
            omitidos_legacy += 1
            continue
        phone = ex.normalize_phone((r.get('telefono') or '').strip())
        if phone and phone in partner_by_phone:
            omitidos_phone += 1
            continue
        if not phone:
            omitidos_invalid += 1
            continue

        detail = detail_cache.get(legacy_id, {})
        email = (detail.get('email') or '').strip()
        if email.lower() in ('no@no.com', 'no@no'):
            email = ''
        medico = (r.get('medico_principal') or '').strip()
        doctor_id = doctor_by_name.get(normalize_name(medico)) if medico else False

        vals = {
            'name': nombre,
            'phone': phone,
            'legacy_id': legacy_id,
            'is_patient': True,
            'source': 'other',  # el Selection no admite 'legacy'
            'is_vip': bool(r.get('vip')),
        }
        if email:
            vals['email'] = email
        if detail.get('fecha_nacimiento'):
            vals['birth_date'] = detail['fecha_nacimiento']
        if detail.get('sexo'):
            vals['gender'] = detail['sexo']
        if doctor_id:
            vals['primary_doctor_id'] = doctor_id
        if (r.get('notas') or '').strip():
            vals['patient_comments'] = r['notas'].strip()

        Partner.create(vals)
        pacientes_nuevos += 1
        partner_by_legacy[legacy_id] = True  # placeholder, id real no necesario abajo
        if phone:
            partner_by_phone[phone] = True
        delta_pacientes.append({'legacy_id': legacy_id, 'nombre': nombre, 'telefono': phone,
                                'folio': r.get('folio', ''), 'vip': '1' if r.get('vip') else '0'})
        if pacientes_nuevos % 50 == 0:
            cr.commit()
            log(f'  {pacientes_nuevos} pacientes nuevos...')
    cr.commit()
    log(f'Pacientes nuevos creados: {pacientes_nuevos} '
        f'(omitidos: {omitidos_legacy} por legacy_id, {omitidos_phone} por teléfono, {omitidos_invalid} inválidos)')

    # Recargar mapa legacy_id → id real (los nuevos ya están en BD)
    cr.execute("SELECT id, legacy_id FROM res_partner WHERE legacy_id IS NOT NULL AND legacy_id != ''")
    partner_by_legacy = {str(lid): pid for pid, lid in cr.fetchall()}

    # ---------- 2. Citas del delta ----------
    log(f'Descargando citas legacy {START} → {END}...')
    delta_rows = []
    current = datetime.strptime(START, '%Y-%m-%d').date()
    while current <= END:
        ds = current.strftime('%Y-%m-%d')
        try:
            citas = ex.fetch_json(f'/agenda/citas/{ds}').get('citas', [])
        except Exception as e:
            log(f'  ERROR {ds}: {e}')
            citas = []
        for c in citas:
            delta_rows.append(c)
        current += timedelta(days=1)
        time.sleep(0.05)
    log(f'Citas legacy descargadas (crudo): {len(delta_rows)}')

    nuevas = 0
    actualizadas = 0
    omitidas_sin_paciente = 0
    omitidas_estado = 0
    ya_existian = 0
    batch = []
    csv_rows = []
    for c in delta_rows:
        nombre = (c.get('nombre') or '').strip()
        if not nombre or nombre.upper().startswith('. . NO CITAR'):
            continue
        status = c.get('status') or 0
        tiene_visita = bool(c.get('visitas_id'))
        state = map_state(status, c.get('confirmada'), tiene_visita)
        if state is None:
            omitidas_estado += 1
            continue

        expediente_id = str(c.get('expedientes_id') or '').strip()
        partner_id = partner_by_legacy.get(expediente_id) if expediente_id else None
        if not partner_id:
            phone = ex.normalize_phone(c.get('telefono') or '') or ex.normalize_phone(c.get('whatsapp') or '')
            partner_id = partner_by_phone.get(phone) if phone else None
        if not partner_id:
            omitidas_sin_paciente += 1
            continue

        fecha = (c.get('fecha') or '').strip()
        if not fecha:
            continue
        hora = round(parse_time_hhmm(c.get('inicio')), 2)
        key = (partner_id, fecha, hora)

        servicio_nombre = extract_service(c.get('titulo', ''), nombre) or 'Servicio genérico'
        medico_id = str(c.get('medicos_id') or '')
        doctor_nombre = medicos_map.get(medico_id, '')
        doctor_id = doctor_by_name.get(normalize_name(doctor_nombre)) if doctor_nombre else False
        notas = html.unescape(c.get('observaciones') or '')

        csv_rows.append({'legacy_id': c.get('id', ''), 'expedientes_id': expediente_id,
                         'paciente': nombre, 'servicio': servicio_nombre, 'fecha': fecha,
                         'hora': c.get('inicio', ''), 'estado': state})

        if key in existing_citas:
            ya_existian += 1
            cid, estado_actual = existing_citas[key]
            if should_update(estado_actual, state):
                Cita.browse(cid).write({'state': state})
                actualizadas += 1
            continue

        batch.append({
            'partner_id': partner_id,
            'servicio_id': get_or_create_service(servicio_nombre),
            'date': fecha,
            'time': hora,
            'state': state,
            'payment_state': 'not_paid',
            'amount_paid': 0,
            'doctor_id': doctor_id,
            'branch': 'rosarito',
            'medium': 'onsite',
            'notes': notas,
        })
        existing_citas[key] = (None, state)  # dedup dentro del mismo delta
        if len(batch) >= 200:
            Cita.create(batch)
            nuevas += len(batch)
            batch = []
            cr.commit()
            log(f'  {nuevas} citas nuevas...')
    if batch:
        Cita.create(batch)
        nuevas += len(batch)
        cr.commit()

    # ---------- Guardar CSVs del delta (archivos nuevos, no toca originales) ----------
    if csv_rows:
        with open(OUTDIR / f'delta_citas_{STAMP}.csv', 'w', newline='', encoding='utf-8') as f:
            w = csv.DictWriter(f, fieldnames=['legacy_id', 'expedientes_id', 'paciente', 'servicio',
                                              'fecha', 'hora', 'estado'], extrasaction='ignore')
            w.writeheader()
            w.writerows(csv_rows)
    if delta_pacientes:
        with open(OUTDIR / f'delta_pacientes_{STAMP}.csv', 'w', newline='', encoding='utf-8') as f:
            w = csv.DictWriter(f, fieldnames=['legacy_id', 'nombre', 'telefono', 'folio', 'vip'])
            w.writeheader()
            w.writerows(delta_pacientes)

    # Restaurar validación y cerrar
    CitaModel._check_availability = original_check
    cr.commit()
    cr.close()

    log('========== RESUMEN DELTA ==========')
    log(f'Pacientes nuevos:        {pacientes_nuevos}')
    log(f'Citas nuevas:            {nuevas}')
    log(f'Citas actualizadas:      {actualizadas}')
    log(f'Citas ya existentes:     {ya_existian}')
    log(f'Citas omitidas (sin paciente): {omitidas_sin_paciente}')
    log(f'Citas omitidas (bloques agenda): {omitidas_estado}')
    log(f'CSVs: extraccion/delta_citas_{STAMP}.csv / delta_pacientes_{STAMP}.csv')


if __name__ == '__main__':
    main()
