# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Migration: Root-Mandant bereinigen.
Moves all end-user data from Root mandate shared instances to own mandates.
Called once from bootstrap, sets a DB flag to prevent re-execution.
"""

import logging
from typing import Optional, List, Dict, Any

logger = logging.getLogger(__name__)

_MIGRATION_FLAG_KEY = "migration_root_users_completed"

_DATA_TABLES = [
    "ChatWorkflow",
    "FileItem",
    "DataSource",
    "DataNeutralizerAttributes",
    "FileContentIndex",
]


def _isMigrationCompleted(db) -> bool:
    """Check if migration has already been executed."""
    try:
        from modules.datamodels.datamodelUam import Mandate
        records = db.getRecordset(Mandate, recordFilter={"name": _MIGRATION_FLAG_KEY})
        return len(records) > 0
    except Exception:
        return False


def _setMigrationCompleted(db) -> None:
    """Set flag that migration is completed (uses a settings-like record)."""
    if _isMigrationCompleted(db):
        return
    try:
        from modules.datamodels.datamodelUam import Mandate
        flag = Mandate(name=_MIGRATION_FLAG_KEY, label="Migration completed", enabled=False, isSystem=True)
        db.recordCreate(Mandate, flag)
        logger.info("Migration flag set: root user migration completed")
    except Exception as e:
        logger.error(f"Failed to set migration flag: {e}")


def _findOrCreateTargetInstance(db, featureInterface, featureCode: str, targetMandateId: str, rootInstance: dict) -> dict:
    """Find existing or create new FeatureInstance in target mandate. Idempotent."""
    from modules.datamodels.datamodelFeatures import FeatureInstance

    existing = db.getRecordset(FeatureInstance, recordFilter={
        "featureCode": featureCode,
        "mandateId": targetMandateId,
    })
    if existing:
        logger.debug(f"Target instance already exists for {featureCode} in mandate {targetMandateId}")
        return existing[0]

    label = rootInstance.get("label") or featureCode
    instance = featureInterface.createFeatureInstance(
        featureCode=featureCode,
        mandateId=targetMandateId,
        label=label,
        enabled=True,
        copyTemplateRoles=True,
    )
    if isinstance(instance, dict):
        return instance
    return instance.model_dump() if hasattr(instance, "model_dump") else {"id": instance.id}


def _migrateDataRecords(db, oldInstanceId: str, newInstanceId: str, userId: str) -> int:
    """Bulk-update featureInstanceId on all data tables for records owned by userId."""
    totalMigrated = 0
    db._ensure_connection()
    for tableName in _DATA_TABLES:
        try:
            with db.connection.cursor() as cursor:
                cursor.execute(
                    f'UPDATE "{tableName}" '
                    f'SET "featureInstanceId" = %s '
                    f'WHERE "featureInstanceId" = %s AND "sysCreatedBy" = %s',
                    (newInstanceId, oldInstanceId, userId),
                )
                count = cursor.rowcount
            db.connection.commit()
            if count > 0:
                logger.info(f"  Migrated {count} rows in {tableName}: {oldInstanceId} -> {newInstanceId}")
                totalMigrated += count
        except Exception as e:
            try:
                db.connection.rollback()
            except Exception:
                pass
            logger.debug(f"  Table {tableName} skipped (may not exist or no matching column): {e}")
    return totalMigrated


def _grantFeatureAccess(db, userId: str, featureInstanceId: str) -> dict:
    """Create FeatureAccess + admin role on a feature instance. Idempotent."""
    from modules.datamodels.datamodelMembership import FeatureAccess, FeatureAccessRole
    from modules.datamodels.datamodelRbac import Role

    existing = db.getRecordset(FeatureAccess, recordFilter={
        "userId": userId,
        "featureInstanceId": featureInstanceId,
    })
    if existing:
        logger.debug(f"FeatureAccess already exists for user {userId} on instance {featureInstanceId}")
        return existing[0]

    fa = FeatureAccess(userId=userId, featureInstanceId=featureInstanceId, enabled=True)
    createdFa = db.recordCreate(FeatureAccess, fa.model_dump())
    if not createdFa:
        logger.warning(f"Failed to create FeatureAccess for user {userId} on instance {featureInstanceId}")
        return {}

    instanceRoles = db.getRecordset(Role, recordFilter={"featureInstanceId": featureInstanceId})
    adminRoleId = None
    for r in instanceRoles:
        roleLabel = (r.get("roleLabel") or "").lower()
        if roleLabel.endswith("-admin"):
            adminRoleId = r.get("id")
            break
    if not adminRoleId:
        raise ValueError(
            f"No feature-specific admin role for instance {featureInstanceId}. "
            f"Cannot create FeatureAccess without role — even in migration context."
        )
    far = FeatureAccessRole(featureAccessId=createdFa["id"], roleId=adminRoleId)
    db.recordCreate(FeatureAccessRole, far.model_dump())

    return createdFa


def migrateRootUsers(db, dryRun: bool = False) -> dict:
    """
    Migrate all end-user feature data from Root mandate to personal mandates.
    
    Algorithm:
    STEP 1: For each user with FeatureAccess on Root instances:
      - If user has own mandate: target = existing mandate
      - If not: create personal mandate via _provisionMandateForUser
      - For each FeatureAccess: create new instance in target, migrate data, transfer access
    
    STEP 2: Clean up Root:
      - Delete all FeatureInstances in Root
      - Remove UserMandate for non-sysadmin users
    
    Args:
        db: Database connector
        dryRun: If True, log actions without making changes
        
    Returns:
        Summary dict with migration statistics
    """
    if _isMigrationCompleted(db):
        logger.info("Root user migration already completed, skipping")
        return {"status": "already_completed"}

    from modules.datamodels.datamodelUam import Mandate, User, UserInDB
    from modules.datamodels.datamodelMembership import (
        UserMandate, UserMandateRole, FeatureAccess, FeatureAccessRole,
    )
    from modules.datamodels.datamodelFeatures import FeatureInstance
    from modules.interfaces.interfaceDbApp import getRootInterface
    from modules.interfaces.interfaceFeatures import getFeatureInterface

    rootInterface = getRootInterface()
    featureInterface = getFeatureInterface(db)
    stats = {
        "usersProcessed": 0,
        "mandatesCreated": 0,
        "instancesMigrated": 0,
        "dataRowsMigrated": 0,
        "rootInstancesDeleted": 0,
        "rootMembershipsRemoved": 0,
        "dryRun": dryRun,
    }

    # Find root mandate
    rootMandates = db.getRecordset(Mandate, recordFilter={"name": "root", "isSystem": True})
    if not rootMandates:
        logger.warning("No root mandate found, nothing to migrate")
        return {"status": "no_root_mandate"}
    rootMandateId = rootMandates[0].get("id")

    # Get all feature instances in root
    rootInstances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": rootMandateId})
    if not rootInstances:
        logger.info("No feature instances in root mandate, nothing to migrate")
        if not dryRun:
            _setMigrationCompleted(db)
        return {"status": "no_instances", **stats}

    # Get all FeatureAccess on root instances
    rootInstanceIds = {inst.get("id") for inst in rootInstances}

    # Collect unique users with access on root instances
    usersToMigrate = {}
    for instanceId in rootInstanceIds:
        accesses = db.getRecordset(FeatureAccess, recordFilter={"featureInstanceId": instanceId})
        for access in accesses:
            userId = access.get("userId")
            if userId not in usersToMigrate:
                usersToMigrate[userId] = []
            usersToMigrate[userId].append({
                "featureAccessId": access.get("id"),
                "featureInstanceId": instanceId,
            })

    logger.info(f"Migration: {len(usersToMigrate)} users with {sum(len(v) for v in usersToMigrate.values())} accesses on {len(rootInstances)} root instances")

    # STEP 1: Migrate users
    for userId, accessList in usersToMigrate.items():
        try:
            # Find user
            users = db.getRecordset(UserInDB, recordFilter={"id": userId})
            if not users:
                logger.warning(f"User {userId} not found, skipping")
                continue
            user = users[0]
            username = user.get("username", "unknown")

            # Check if user has own non-root mandate
            userMandates = db.getRecordset(UserMandate, recordFilter={"userId": userId, "enabled": True})
            targetMandateId = None
            for um in userMandates:
                mid = um.get("mandateId")
                if mid != rootMandateId:
                    targetMandateId = mid
                    break

            if not targetMandateId:
                # Create personal mandate
                if dryRun:
                    logger.info(f"[DRY RUN] Would create personal mandate for user {username}")
                    stats["mandatesCreated"] += 1
                else:
                    try:
                        result = rootInterface._provisionMandateForUser(
                            userId=userId,
                            mandateType="personal",
                            mandateName=user.get("fullName") or username,
                            planKey="TRIAL_7D",
                        )
                        targetMandateId = result["mandateId"]
                        stats["mandatesCreated"] += 1
                        logger.info(f"Created personal mandate {targetMandateId} for user {username}")
                    except Exception as e:
                        logger.error(f"Failed to create mandate for user {username}: {e}")
                        continue

            # Migrate each FeatureAccess
            for accessInfo in accessList:
                oldInstanceId = accessInfo["featureInstanceId"]
                oldAccessId = accessInfo["featureAccessId"]

                # Find the root instance details
                instRecords = db.getRecordset(FeatureInstance, recordFilter={"id": oldInstanceId})
                if not instRecords:
                    continue
                featureCode = instRecords[0].get("featureCode")

                if dryRun:
                    logger.info(f"[DRY RUN] Would migrate {featureCode} for {username} to mandate {targetMandateId}")
                    stats["instancesMigrated"] += 1
                else:
                    targetInstance = _findOrCreateTargetInstance(
                        db, featureInterface, featureCode, targetMandateId, instRecords[0],
                    )
                    newInstanceId = targetInstance.get("id")
                    if not newInstanceId:
                        logger.error(f"Failed to obtain target instance for {featureCode} in mandate {targetMandateId}")
                        continue

                    migratedCount = _migrateDataRecords(db, oldInstanceId, newInstanceId, userId)

                    _grantFeatureAccess(db, userId, newInstanceId)

                    try:
                        db.recordDelete(FeatureAccess, oldAccessId)
                    except Exception as delErr:
                        logger.warning(f"Could not remove old FeatureAccess {oldAccessId}: {delErr}")

                    logger.info(
                        f"Migrated {featureCode} for {username}: "
                        f"instance {oldInstanceId} -> {newInstanceId}, {migratedCount} data rows moved"
                    )
                    stats["instancesMigrated"] += 1
                    stats["dataRowsMigrated"] += migratedCount

            stats["usersProcessed"] += 1

        except Exception as e:
            logger.error(f"Error migrating user {userId}: {e}")

    # STEP 2: Clean up root
    if not dryRun:
        # Delete all feature instances in root
        for inst in rootInstances:
            instId = inst.get("id")
            try:
                # First delete all FeatureAccess on this instance
                accesses = db.getRecordset(FeatureAccess, recordFilter={"featureInstanceId": instId})
                for access in accesses:
                    db.recordDelete(FeatureAccess, access.get("id"))
                db.recordDelete(FeatureInstance, instId)
                stats["rootInstancesDeleted"] += 1
            except Exception as e:
                logger.error(f"Error deleting root instance {instId}: {e}")

        # Remove non-sysadmin users from root mandate
        rootMembers = db.getRecordset(UserMandate, recordFilter={"mandateId": rootMandateId})
        for membership in rootMembers:
            membUserId = membership.get("userId")
            userRecords = db.getRecordset(UserInDB, recordFilter={"id": membUserId})
            if userRecords and userRecords[0].get("isSysAdmin"):
                continue
            try:
                db.recordDelete(UserMandate, membership.get("id"))
                stats["rootMembershipsRemoved"] += 1
            except Exception as e:
                logger.error(f"Error removing root membership for {membUserId}: {e}")

        _setMigrationCompleted(db)

    logger.info(f"Migration completed: {stats}")
    return {"status": "completed", **stats}
