import {
    AppConnectionWithoutSensitiveData,
    BaseModelSchema,
    Flow,
    FlowOperationRequest,
    FlowOperationType,
    FlowRun,
    FlowVersion,
    Folder,
    Project,
    ProjectRelease,
    ProjectRole,
    User,
} from '@activepieces/shared'
import { Static, Type } from '@sinclair/typebox'
import { SigningKey } from '../signing-key'
export const ListAuditEventsRequest = Type.Object({
    limit: Type.Optional(Type.Number()),
    cursor: Type.Optional(Type.String()),
    action: Type.Optional(Type.Array(Type.String())),
    projectId: Type.Optional(Type.Array(Type.String())),
    userId: Type.Optional(Type.String()),
    createdBefore: Type.Optional(Type.String()),
    createdAfter: Type.Optional(Type.String()),
})

export type ListAuditEventsRequest = Static<typeof ListAuditEventsRequest>

const UserMeta = Type.Pick(User, ['email', 'id', 'firstName', 'lastName'])

export enum ApplicationEventName {
    FLOW_CREATED = 'flow.created',
    FLOW_DELETED = 'flow.deleted',
    FLOW_UPDATED = 'flow.updated',
    FLOW_RUN_STARTED = 'flow.run.started',
    FLOW_RUN_FINISHED = 'flow.run.finished',
    FOLDER_CREATED = 'folder.created',
    FOLDER_UPDATED = 'folder.updated',
    FOLDER_DELETED = 'folder.deleted',
    CONNECTION_UPSERTED = 'connection.upserted',
    CONNECTION_DELETED = 'connection.deleted',
    USER_SIGNED_UP = 'user.signed.up',
    USER_SIGNED_IN = 'user.signed.in',
    USER_PASSWORD_RESET = 'user.password.reset',
    USER_EMAIL_VERIFIED = 'user.email.verified',
    SIGNING_KEY_CREATED = 'signing.key.created',
    PROJECT_ROLE_CREATED = 'project.role.created',
    PROJECT_ROLE_DELETED = 'project.role.deleted',
    PROJECT_ROLE_UPDATED = 'project.role.updated',
    PROJECT_RELEASE_CREATED = 'project.release.created',
}

const BaseAuditEventProps = {
    ...BaseModelSchema,
    platformId: Type.String(),
    projectId: Type.Optional(Type.String()),
    projectDisplayName: Type.Optional(Type.String()),
    userId: Type.Optional(Type.String()),
    userEmail: Type.Optional(Type.String()),
    ip: Type.Optional(Type.String()),
}

export const ConnectionEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Union([
        Type.Literal(ApplicationEventName.CONNECTION_DELETED),
        Type.Literal(ApplicationEventName.CONNECTION_UPSERTED),
    ]),
    data: Type.Object({
        connection: Type.Pick(AppConnectionWithoutSensitiveData, [
            'displayName',
            'externalId',
            'pieceName',
            'status',
            'type',
            'id',
            'created',
            'updated',
        ]),
        project: Type.Optional(Type.Pick(Project, ['displayName'])),
    }),
})
export type ConnectionEvent = Static<typeof ConnectionEvent>

export const FolderEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Union([
        Type.Literal(ApplicationEventName.FOLDER_UPDATED),
        Type.Literal(ApplicationEventName.FOLDER_CREATED),
        Type.Literal(ApplicationEventName.FOLDER_DELETED),
    ]),
    data: Type.Object({
        folder: Type.Pick(Folder, ['id', 'displayName', 'created', 'updated']),
        project: Type.Optional(Type.Pick(Project, ['displayName'])),
    }),
})

export type FolderEvent = Static<typeof FolderEvent>

export const FlowRunEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Union([
        Type.Literal(ApplicationEventName.FLOW_RUN_STARTED),
        Type.Literal(ApplicationEventName.FLOW_RUN_FINISHED),
    ]),
    data: Type.Object({
        flowRun: Type.Pick(FlowRun, [
            'id',
            'startTime',
            'finishTime',
            'duration',
            'environment',
            'flowId',
            'flowVersionId',
            'flowDisplayName',
            'status',
        ]),
        project: Type.Optional(Type.Pick(Project, ['displayName'])),
    }),
})
export type FlowRunEvent = Static<typeof FlowRunEvent>

export const FlowCreatedEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Literal(ApplicationEventName.FLOW_CREATED),
    data: Type.Object({
        flow: Type.Pick(Flow, ['id', 'created', 'updated']),
        project: Type.Optional(Type.Pick(Project, ['displayName'])),
    }),
})

export type FlowCreatedEvent = Static<typeof FlowCreatedEvent>

export const FlowDeletedEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Literal(ApplicationEventName.FLOW_DELETED),
    data: Type.Object({
        flow: Type.Pick(Flow, ['id', 'created', 'updated']),
        flowVersion: Type.Pick(FlowVersion, [
            'id',
            'displayName',
            'flowId',
            'created',
            'updated',
        ]),
        project: Type.Optional(Type.Pick(Project, ['displayName'])),
    }),
})

export type FlowDeletedEvent = Static<typeof FlowDeletedEvent>

export const FlowUpdatedEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Literal(ApplicationEventName.FLOW_UPDATED),
    data: Type.Object({
        flowVersion: Type.Pick(FlowVersion, [
            'id',
            'displayName',
            'flowId',
            'created',
            'updated',
        ]),
        request: FlowOperationRequest,
        project: Type.Optional(Type.Pick(Project, ['displayName'])),
    }),
})

export type FlowUpdatedEvent = Static<typeof FlowUpdatedEvent>

export const AuthenticationEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Union([
        Type.Literal(ApplicationEventName.USER_SIGNED_IN),
        Type.Literal(ApplicationEventName.USER_PASSWORD_RESET),
        Type.Literal(ApplicationEventName.USER_EMAIL_VERIFIED),
    ]),
    data: Type.Object({
        user: Type.Optional(UserMeta),
    }),
})

export type AuthenticationEvent = Static<typeof AuthenticationEvent>

export const SignUpEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Literal(ApplicationEventName.USER_SIGNED_UP),
    data: Type.Object({
        source: Type.Union([
            Type.Literal('credentials'),
            Type.Literal('sso'),
            Type.Literal('managed'),
        ]),
        user: Type.Optional(UserMeta),
    }),
})
export type SignUpEvent = Static<typeof SignUpEvent>

export const SigningKeyEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Union([Type.Literal(ApplicationEventName.SIGNING_KEY_CREATED)]),
    data: Type.Object({
        signingKey: Type.Pick(SigningKey, [
            'id',
            'created',
            'updated',
            'displayName',
        ]),
    }),
})

export type SigningKeyEvent = Static<typeof SigningKeyEvent>

export const ProjectRoleEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Union([
        Type.Literal(ApplicationEventName.PROJECT_ROLE_CREATED),
        Type.Literal(ApplicationEventName.PROJECT_ROLE_UPDATED),
        Type.Literal(ApplicationEventName.PROJECT_ROLE_DELETED),
    ]),
    data: Type.Object({
        projectRole: Type.Pick(ProjectRole, [
            'id',
            'created',
            'updated',
            'name',
            'permissions',
            'platformId',
        ]),
    }),
})

export type ProjectRoleEvent = Static<typeof ProjectRoleEvent>

export const ProjectReleaseEvent = Type.Object({
    ...BaseAuditEventProps,
    action: Type.Literal(ApplicationEventName.PROJECT_RELEASE_CREATED),
    data: Type.Object({
        release: Type.Pick(ProjectRelease, ['name', 'description', 'type', 'projectId', 'importedByUser']),
    }),
})

export type ProjectReleaseEvent = Static<typeof ProjectReleaseEvent>

export const ApplicationEvent = Type.Union([
    ConnectionEvent,
    FlowCreatedEvent,
    FlowDeletedEvent,
    FlowUpdatedEvent,
    FlowRunEvent,
    AuthenticationEvent,
    FolderEvent,
    SignUpEvent,
    SigningKeyEvent,
    ProjectRoleEvent,
    ProjectReleaseEvent,
])

export type ApplicationEvent = Static<typeof ApplicationEvent>

export function summarizeApplicationEvent(event: ApplicationEvent) {
    switch (event.action) {
        case ApplicationEventName.FLOW_UPDATED: {
            return convertUpdateActionToDetails(event)
        }
        case ApplicationEventName.FLOW_RUN_STARTED:
            return `Flow run ${event.data.flowRun.id} is started`
        case ApplicationEventName.FLOW_RUN_FINISHED: {
            return `Flow run ${event.data.flowRun.id} is finished`
        }
        case ApplicationEventName.FLOW_CREATED:
            return `Flow ${event.data.flow.id} is created`
        case ApplicationEventName.FLOW_DELETED:
            return `Flow ${event.data.flow.id} (${event.data.flowVersion.displayName}) is deleted`
        case ApplicationEventName.FOLDER_CREATED:
            return `${event.data.folder.displayName} is created`
        case ApplicationEventName.FOLDER_UPDATED:
            return `${event.data.folder.displayName} is updated`
        case ApplicationEventName.FOLDER_DELETED:
            return `${event.data.folder.displayName} is deleted`
        case ApplicationEventName.CONNECTION_UPSERTED:
            return `${event.data.connection.displayName} (${event.data.connection.externalId}) is updated`
        case ApplicationEventName.CONNECTION_DELETED:
            return `${event.data.connection.displayName} (${event.data.connection.externalId}) is deleted`
        case ApplicationEventName.USER_SIGNED_IN:
            return `User ${event.userEmail} signed in`
        case ApplicationEventName.USER_PASSWORD_RESET:
            return `User ${event.userEmail} reset password`
        case ApplicationEventName.USER_EMAIL_VERIFIED:
            return `User ${event.userEmail} verified email`
        case ApplicationEventName.USER_SIGNED_UP:
            return `User ${event.userEmail} signed up using email from ${event.data.source}`
        case ApplicationEventName.SIGNING_KEY_CREATED:
            return `${event.data.signingKey.displayName} is created`
        case ApplicationEventName.PROJECT_ROLE_CREATED:
            return `${event.data.projectRole.name} is created`
        case ApplicationEventName.PROJECT_ROLE_UPDATED:
            return `${event.data.projectRole.name} is updated`
        case ApplicationEventName.PROJECT_ROLE_DELETED:
            return `${event.data.projectRole.name} is deleted`
        case ApplicationEventName.PROJECT_RELEASE_CREATED:
            return `${event.data.release.name} is created`
    }
}

function convertUpdateActionToDetails(event: FlowUpdatedEvent) {
    switch (event.data.request.type) {
        case FlowOperationType.ADD_ACTION:
            return `Added action "${event.data.request.request.action.displayName}" to "${event.data.flowVersion.displayName}" Flow.`
        case FlowOperationType.UPDATE_ACTION:
            return `Updated action "${event.data.request.request.displayName}" in "${event.data.flowVersion.displayName}" Flow.`
        case FlowOperationType.DELETE_ACTION:
        {
            const request = event.data.request.request
            const names = request.names
            return `Deleted actions "${names.join(', ')}" from "${event.data.flowVersion.displayName}" Flow.`
        }
        case FlowOperationType.CHANGE_NAME:
            return `Renamed flow "${event.data.flowVersion.displayName}" to "${event.data.request.request.displayName}".`
        case FlowOperationType.LOCK_AND_PUBLISH:
            return `Locked and published flow "${event.data.flowVersion.displayName}" Flow.`
        case FlowOperationType.USE_AS_DRAFT:
            return `Unlocked and unpublished flow "${event.data.flowVersion.displayName}" Flow.`
        case FlowOperationType.MOVE_ACTION:
            return `Moved action "${event.data.request.request.name}" to after "${event.data.request.request.newParentStep}".`
        case FlowOperationType.LOCK_FLOW:
            return `Locked flow "${event.data.flowVersion.displayName}" Flow.`
        case FlowOperationType.CHANGE_STATUS:
            return `Changed status of flow "${event.data.flowVersion.displayName}" Flow to "${event.data.request.request.status}".`
        case FlowOperationType.DUPLICATE_ACTION:
            return `Duplicated action "${event.data.request.request.stepName}" in "${event.data.flowVersion.displayName}" Flow.`
        case FlowOperationType.IMPORT_FLOW:
            return `Imported flow in "${event.data.request.request.displayName}" Flow.`
        case FlowOperationType.UPDATE_TRIGGER:
            return `Updated trigger in "${event.data.flowVersion.displayName}" Flow to "${event.data.request.request.displayName}".`
        case FlowOperationType.CHANGE_FOLDER:
            return `Moved flow "${event.data.flowVersion.displayName}" to folder id ${event.data.request.request.folderId}.`
        case FlowOperationType.DELETE_BRANCH: {
            return `Deleted branch number ${
                event.data.request.request.branchIndex + 1
            } in flow "${event.data.flowVersion.displayName}" for the step "${
                event.data.request.request.stepName
            }".`
        }
        case FlowOperationType.SAVE_SAMPLE_DATA: {
            return `Saved sample data for step "${event.data.request.request.stepName}" in flow "${event.data.flowVersion.displayName}".`
        }
        case FlowOperationType.DUPLICATE_BRANCH: {
            return `Duplicated branch number ${
                event.data.request.request.branchIndex + 1
            } in flow "${event.data.flowVersion.displayName}" for the step "${
                event.data.request.request.stepName
            }".`
        }
        case FlowOperationType.ADD_BRANCH:
            return `Added branch number ${
                event.data.request.request.branchIndex + 1
            } in flow "${event.data.flowVersion.displayName}" for the step "${
                event.data.request.request.stepName
            }".`
        case FlowOperationType.SET_SKIP_ACTION:
        {
            const request = event.data.request.request
            const names = request.names
            return `Updated actions "${names.join(', ')}" in "${event.data.flowVersion.displayName}" Flow to skip.`
        }
        case FlowOperationType.UPDATE_METADATA:
            return `Updated metadata for flow "${event.data.flowVersion.displayName}".`
        case FlowOperationType.MOVE_BRANCH:
            return `Moved branch number ${
                event.data.request.request.sourceBranchIndex + 1
            } to ${
                event.data.request.request.targetBranchIndex + 1
            } in flow "${event.data.flowVersion.displayName}" for the step "${
                event.data.request.request.stepName
            }".`
    }
}