import os
import secrets
from flask import Flask, render_template, request, session, abort
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from dotenv import load_dotenv
from flask_socketio import SocketIO
from sqlalchemy import inspect, text as sql_text

load_dotenv()

db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = 'login'
login_manager.login_message = 'Iniciá sesión para continuar.'
socketio = SocketIO(cors_allowed_origins="*")


def ensure_runtime_schema(app):
    with app.app_context():
        inspector = inspect(db.engine)
        def has_column(table_name, column_name):
            try:
                return any(col.get('name') == column_name for col in inspector.get_columns(table_name))
            except Exception:
                return False
        patches = [
            ('saas_plan','name',"CREATE TABLE IF NOT EXISTS saas_plan (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, name VARCHAR(140) NOT NULL UNIQUE, description TEXT, monthly_price FLOAT DEFAULT 0, price_per_appointment FLOAT DEFAULT 0, included_professionals INTEGER DEFAULT 1, extra_professional_price FLOAT DEFAULT 0, included_appointments INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)"),
            ('institution_subscription','institution_id',"CREATE TABLE IF NOT EXISTS institution_subscription (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, institution_id INTEGER NOT NULL, plan_id INTEGER NOT NULL, start_date DATE, billing_day INTEGER DEFAULT 1, status VARCHAR(30) DEFAULT 'activa', notes TEXT)"),
            ('saas_invoice','institution_id',"CREATE TABLE IF NOT EXISTS saas_invoice (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, institution_id INTEGER NOT NULL, subscription_id INTEGER, period VARCHAR(7) NOT NULL, issue_date DATE, due_date DATE, monthly_amount FLOAT DEFAULT 0, appointment_count INTEGER DEFAULT 0, appointment_amount FLOAT DEFAULT 0, professional_count INTEGER DEFAULT 0, extra_professional_amount FLOAT DEFAULT 0, total_amount FLOAT DEFAULT 0, paid_amount FLOAT DEFAULT 0, status VARCHAR(30) DEFAULT 'pendiente', payment_method VARCHAR(40), mercadopago_preference_id VARCHAR(160), mercadopago_payment_id VARCHAR(160), notes TEXT)"),
            ('saas_payment','invoice_id',"CREATE TABLE IF NOT EXISTS saas_payment (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, invoice_id INTEGER NOT NULL, payment_date DATE, amount FLOAT DEFAULT 0, method VARCHAR(40) DEFAULT 'efectivo', reference VARCHAR(160), notes TEXT, created_by_user_id INTEGER)"),
            ('saas_invoice','mercadopago_status','ALTER TABLE saas_invoice ADD COLUMN mercadopago_status VARCHAR(80)'),
            ('saas_invoice','mercadopago_status_detail','ALTER TABLE saas_invoice ADD COLUMN mercadopago_status_detail VARCHAR(160)'),
            ('saas_invoice','mercadopago_init_point','ALTER TABLE saas_invoice ADD COLUMN mercadopago_init_point VARCHAR(600)'),
            ('saas_invoice','mercadopago_sandbox_init_point','ALTER TABLE saas_invoice ADD COLUMN mercadopago_sandbox_init_point VARCHAR(600)'),
            ('saas_invoice','mercadopago_payload','ALTER TABLE saas_invoice ADD COLUMN mercadopago_payload TEXT'),
            ('saas_payment','status',"ALTER TABLE saas_payment ADD COLUMN status VARCHAR(40) DEFAULT 'registrado'"),
            ('saas_payment','mercadopago_payment_id','ALTER TABLE saas_payment ADD COLUMN mercadopago_payment_id VARCHAR(160)'),
            ('saas_payment','mercadopago_status','ALTER TABLE saas_payment ADD COLUMN mercadopago_status VARCHAR(80)'),
            ('saas_payment','mercadopago_status_detail','ALTER TABLE saas_payment ADD COLUMN mercadopago_status_detail VARCHAR(160)'),
            ('saas_payment','mercadopago_payload','ALTER TABLE saas_payment ADD COLUMN mercadopago_payload TEXT'),
            ('institution','name',"CREATE TABLE IF NOT EXISTS institution (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, name VARCHAR(180) NOT NULL, cuit VARCHAR(30), domicilio VARCHAR(220), ciudad VARCHAR(120), provincia VARCHAR(120), telefono VARCHAR(80), responsable VARCHAR(160), situacion_juridica VARCHAR(120), nro_habilitacion VARCHAR(120), expediente_autorizacion VARCHAR(160), email VARCHAR(160), telefono_responsable VARCHAR(80), logo_path VARCHAR(255), active BOOLEAN DEFAULT 1)"),
            ('institution_branch','name',"CREATE TABLE IF NOT EXISTS institution_branch (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, institution_id INTEGER NOT NULL, name VARCHAR(160) NOT NULL, address VARCHAR(220), province VARCHAR(120), city VARCHAR(120), phone VARCHAR(80), email VARCHAR(160), notes TEXT, active BOOLEAN DEFAULT 1)"),
            ('appointment','branch_id','ALTER TABLE appointment ADD COLUMN branch_id INTEGER'),
            ('user','institution_id','ALTER TABLE user ADD COLUMN institution_id INTEGER'),
            ('professional_profile','institution_id','ALTER TABLE professional_profile ADD COLUMN institution_id INTEGER'),
            ('patient','institution_id','ALTER TABLE patient ADD COLUMN institution_id INTEGER'),
            ('service','institution_id','ALTER TABLE service ADD COLUMN institution_id INTEGER'),
            ('appointment','institution_id','ALTER TABLE appointment ADD COLUMN institution_id INTEGER'),
            ('prescription','institution_id','ALTER TABLE prescription ADD COLUMN institution_id INTEGER'),
            ('clinical_record','institution_id','ALTER TABLE clinical_record ADD COLUMN institution_id INTEGER'),
            ('clinical_episode','institution_id','ALTER TABLE clinical_episode ADD COLUMN institution_id INTEGER'),
            ('clinical_entry','institution_id','ALTER TABLE clinical_entry ADD COLUMN institution_id INTEGER'),
            ('clinical_episode_template','title',"CREATE TABLE IF NOT EXISTS clinical_episode_template (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, professional_id INTEGER NOT NULL, created_by_user_id INTEGER, title VARCHAR(180) NOT NULL, category VARCHAR(120) DEFAULT 'General', specialty_name VARCHAR(150), reason VARCHAR(255), diagnosis_summary VARCHAR(255), care_level VARCHAR(80) DEFAULT 'Ambulatorio', visibility_scope VARCHAR(30) DEFAULT 'Institucional', notes TEXT, source_episode_id INTEGER, usage_count INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT 1, institution_id INTEGER)"),
            ('accounting_entry','institution_id','ALTER TABLE accounting_entry ADD COLUMN institution_id INTEGER'),
            ('chat_conversation','institution_id','ALTER TABLE chat_conversation ADD COLUMN institution_id INTEGER'),
            ('whatsapp_bot_rule','institution_id','ALTER TABLE whatsapp_bot_rule ADD COLUMN institution_id INTEGER'),
            ('whatsapp_bot_knowledge','institution_id','ALTER TABLE whatsapp_bot_knowledge ADD COLUMN institution_id INTEGER'),
            ('whatsapp_message_log','institution_id','ALTER TABLE whatsapp_message_log ADD COLUMN institution_id INTEGER'),

            ('clinical_record','summary_payload','ALTER TABLE clinical_record ADD COLUMN summary_payload TEXT'),
            ('clinical_entry','chief_complaint','ALTER TABLE clinical_entry ADD COLUMN chief_complaint VARCHAR(255)'),
            ('clinical_entry','provisional_diagnosis','ALTER TABLE clinical_entry ADD COLUMN provisional_diagnosis VARCHAR(255)'),
            ('clinical_entry','specialty_template',"ALTER TABLE clinical_entry ADD COLUMN specialty_template VARCHAR(80) DEFAULT 'general_medicine'"),
            ('clinical_entry','vitals_payload','ALTER TABLE clinical_entry ADD COLUMN vitals_payload TEXT'),
            ('clinical_entry','structured_payload','ALTER TABLE clinical_entry ADD COLUMN structured_payload TEXT'),
            ('clinical_entry','entry_status',"ALTER TABLE clinical_entry ADD COLUMN entry_status VARCHAR(30) DEFAULT 'Firmado'"),
            ('clinical_entry','locked_at','ALTER TABLE clinical_entry ADD COLUMN locked_at DATETIME'),
            ('clinical_entry','episode_id','ALTER TABLE clinical_entry ADD COLUMN episode_id INTEGER'),
            ('clinical_entry','signature_payload','ALTER TABLE clinical_entry ADD COLUMN signature_payload TEXT'),
            ('clinical_entry','edit_revision','ALTER TABLE clinical_entry ADD COLUMN edit_revision INTEGER DEFAULT 1'),
            ('clinical_entry','last_edited_at','ALTER TABLE clinical_entry ADD COLUMN last_edited_at DATETIME'),
            ('clinical_entry','last_edited_by_user_id','ALTER TABLE clinical_entry ADD COLUMN last_edited_by_user_id INTEGER'),
            ('clinical_attachment','is_patient_visible','ALTER TABLE clinical_attachment ADD COLUMN is_patient_visible BOOLEAN DEFAULT 0'),
            ('prescription','document_kind',"ALTER TABLE prescription ADD COLUMN document_kind VARCHAR(30) DEFAULT 'recipe'"),
            ('prescription','document_title','ALTER TABLE prescription ADD COLUMN document_title VARCHAR(255)'),
            ('prescription','document_body','ALTER TABLE prescription ADD COLUMN document_body TEXT'),
            ('prescription','portal_visible','ALTER TABLE prescription ADD COLUMN portal_visible BOOLEAN DEFAULT 1'),
            ('whatsapp_bot_rule','trigger',"CREATE TABLE IF NOT EXISTS whatsapp_bot_rule (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, trigger VARCHAR(120) NOT NULL, title VARCHAR(180), response_html TEXT NOT NULL, match_mode VARCHAR(30) DEFAULT 'contains', sort_order INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT 1)"),
            ('whatsapp_bot_knowledge','content',"CREATE TABLE IF NOT EXISTS whatsapp_bot_knowledge (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, source_url VARCHAR(500), title VARCHAR(255), content TEXT NOT NULL, keywords VARCHAR(500), is_active BOOLEAN DEFAULT 1)"),
            ('whatsapp_message_log','wa_id',"CREATE TABLE IF NOT EXISTS whatsapp_message_log (id INTEGER PRIMARY KEY, created_at DATETIME, updated_at DATETIME, wa_id VARCHAR(80), customer_name VARCHAR(180), incoming_text TEXT, outgoing_text TEXT, provider_message_id VARCHAR(120), status VARCHAR(40) DEFAULT 'received', raw_payload TEXT, error TEXT)"),
        ]
        for table_name, column_name, ddl in patches:
            if not has_column(table_name, column_name):
                db.session.execute(sql_text(ddl))
        db.session.commit()
        existing_tables = inspect(db.engine).get_table_names()
        if 'clinical_attachment' not in existing_tables or 'clinical_episode' not in existing_tables or 'clinical_episode_member' not in existing_tables or 'clinical_episode_template' not in existing_tables or 'institution_branch' not in existing_tables:
            db.create_all()


def ensure_default_institution_links():
    from .models import Institution, InstitutionBranch, User, ProfessionalProfile, Patient, Service, Appointment, Prescription, ClinicalRecord, ClinicalEpisode, ClinicalEpisodeTemplate, ClinicalEntry, AccountingEntry, ChatConversation, WhatsappBotRule, WhatsappBotKnowledge, WhatsappMessageLog
    default = Institution.query.order_by(Institution.id.asc()).first()
    if not default:
        default = Institution(name='Institución Demo', cuit='00-00000000-0', email='contacto@demo.com', telefono='11-5555-0000', active=True)
        db.session.add(default)
        db.session.flush()
    for model in [User, ProfessionalProfile, Patient, Service, Appointment, Prescription, ClinicalRecord, ClinicalEpisode, ClinicalEpisodeTemplate, ClinicalEntry, AccountingEntry, ChatConversation, WhatsappBotRule, WhatsappBotKnowledge, WhatsappMessageLog]:
        if hasattr(model, 'institution_id'):
            model.query.filter(model.institution_id.is_(None)).update({model.institution_id: default.id}, synchronize_session=False)
    for prof in ProfessionalProfile.query.all():
        if prof.user and not prof.user.institution_id and prof.institution_id:
            prof.user.institution_id = prof.institution_id
    for inst in Institution.query.all():
        if InstitutionBranch.query.filter_by(institution_id=inst.id).count() == 0:
            db.session.add(InstitutionBranch(
                institution_id=inst.id,
                name='Sede principal',
                address=inst.domicilio,
                city=inst.ciudad,
                province=inst.provincia,
                phone=inst.telefono,
                email=inst.email,
                active=True,
            ))
    db.session.commit()

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-change-me')
    app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///book_appointments.db')
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    app.config['APP_NAME'] = os.getenv('APP_NAME', 'Book Appointments Pro')
    app.config['BASE_URL'] = os.getenv('BASE_URL', 'http://127.0.0.1:5000')
    app.config['REMINDER_WINDOW_HOURS'] = int(os.getenv('REMINDER_WINDOW_HOURS', '24'))
    app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads')
    app.config['BACKUP_FOLDER'] = os.path.join(app.root_path, '..', 'backups')
    app.config['GENERATED_FOLDER'] = os.path.join(app.root_path, 'static', 'generated')
    app.config['MAX_CONTENT_LENGTH'] = int(os.getenv('MAX_CONTENT_LENGTH', str(16 * 1024 * 1024)))
    app.config['SESSION_COOKIE_HTTPONLY'] = True
    app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
    app.config['SESSION_COOKIE_SECURE'] = os.getenv('SESSION_COOKIE_SECURE', '0') == '1'
    app.config['REMEMBER_COOKIE_HTTPONLY'] = True
    app.config['REMEMBER_COOKIE_SAMESITE'] = 'Lax'
    app.config['REMEMBER_COOKIE_SECURE'] = os.getenv('SESSION_COOKIE_SECURE', '0') == '1'

    os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
    os.makedirs(app.config['BACKUP_FOLDER'], exist_ok=True)
    os.makedirs(app.config['GENERATED_FOLDER'], exist_ok=True)

    db.init_app(app)
    socketio.init_app(app)
    login_manager.init_app(app)

    from .models import User, Patient
    from .utils import get_due_reminders, system_log_action, ensure_default_settings, store_error
    from .integrations import sync_obras_sociales

    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

    from .routes import register_routes
    register_routes(app)


    def ensure_csrf_token():
        token = session.get('_csrf_token')
        if not token:
            token = secrets.token_urlsafe(32)
            session['_csrf_token'] = token
        return token

    @app.context_processor
    def inject_security_tokens():
        return {'csrf_token': ensure_csrf_token()}

    @app.before_request
    def validate_csrf_token():
        ensure_csrf_token()
        if request.path.startswith('/webhooks/whatsapp') or request.path.startswith('/webhooks/mercadopago'):
            return
        if request.method in {'POST', 'PUT', 'PATCH', 'DELETE'}:
            token = request.form.get('csrf_token') or request.headers.get('X-CSRF-Token') or ''
            if token != session.get('_csrf_token'):
                abort(400, description='Token CSRF inválido o ausente.')

    @app.after_request
    def apply_security_headers(response):
        response.headers.setdefault('X-Content-Type-Options', 'nosniff')
        response.headers.setdefault('X-Frame-Options', 'SAMEORIGIN')
        response.headers.setdefault('Referrer-Policy', 'strict-origin-when-cross-origin')
        response.headers.setdefault('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
        response.headers.setdefault('Cross-Origin-Opener-Policy', 'same-origin-allow-popups')
        return response

    @app.errorhandler(500)
    def handle_500(error):
        try:
            db.session.rollback()
            store_error('internal_server_error', error, extra=f'{request.method} {request.path}')
        except Exception:
            db.session.rollback()
        return render_template('error.html', message='Se produjo un error interno. El evento quedó registrado en Logs.'), 500

    @app.cli.command('init-db')
    def init_db_command():
        from .seed import seed_demo_data
        with app.app_context():
            db.drop_all()
            db.create_all()
            ensure_default_settings()
            seed_demo_data()
            print('Base de datos creada y datos demo cargados.')

    @app.cli.command('send-reminders')
    def send_reminders_command():
        with app.app_context():
            due = get_due_reminders(app.config['REMINDER_WINDOW_HOURS'])
            if not due:
                print('No hay recordatorios pendientes dentro de la ventana configurada.')
                return
            for appt in due:
                appt.reminder_sent = True
                print(f"REMINDER -> {appt.client_email} | {appt.client_name} | {appt.appointment_date} {appt.start_time.strftime('%H:%M')} | {appt.service.name}")
                system_log_action('reminder_sent', 'Appointment', appt.id, f'Recordatorio simulado a {appt.client_email}')
            db.session.commit()
            print(f'Se marcaron {len(due)} recordatorios como enviados.')

    @app.cli.command('sync-obras-sociales')
    def sync_obras_sociales_command():
        with app.app_context():
            ensure_default_settings()
            result = sync_obras_sociales()
            print(result)

    with app.app_context():
        db.create_all()
        ensure_runtime_schema(app)
        ensure_default_settings()
        ensure_default_institution_links()
        try:
            for patient in Patient.query.all():
                patient_email = (patient.email or '').strip().lower()
                if not patient_email:
                    continue
                user = User.query.filter_by(email=patient_email).first()
                if not user:
                    user = User.query.filter_by(role='client', full_name=patient.nombre_completo).first()
                if user and user.role != 'client':
                    continue
                created = False
                if not user:
                    user = User(full_name=patient.nombre_completo, email=patient_email, role='client', is_active_user=True)
                    user.set_password((patient.documento or 'Cambio1234').strip())
                    db.session.add(user)
                    created = True
                else:
                    user.full_name = patient.nombre_completo
                    user.email = patient_email
                    user.role = 'client'
                    user.is_active_user = True
                if created:
                    db.session.flush()
            db.session.commit()
        except Exception:
            db.session.rollback()
    
    return app
