import type { GatewayPrisma, GatewayPrismaClient } from '@sammo-ts/infra';

import {
    CONTROL_PLANE_OPERATION_CLAIM_LOCK,
    type GatewayOperationStatus,
    type GatewaySourceMode,
} from './profileRepository.js';

export type GatewayReleaseOperationType = 'DEPLOY' | 'ROLLBACK';

export interface GatewayReleaseStateRecord {
    id: string;
    activeCommitSha?: string;
    activeWorkspace?: string;
    previousCommitSha?: string;
    previousWorkspace?: string;
    lastSuccessfulAt?: string;
    lastError?: string;
    updatedAt: string;
}

export interface GatewayReleaseOperationRecord {
    id: string;
    type: GatewayReleaseOperationType;
    status: GatewayOperationStatus;
    sourceMode?: GatewaySourceMode;
    sourceRef?: string;
    resolvedCommitSha?: string;
    payload: GatewayPrisma.JsonObject;
    reason?: string;
    requestedBy: string;
    startedAt?: string;
    completedAt?: string;
    error?: string;
    leaseOwner?: string;
    leaseUntil?: string;
    heartbeatAt?: string;
    attempts: number;
    createdAt: string;
    updatedAt: string;
}

export interface GatewayReleaseOperationCreateInput {
    type: GatewayReleaseOperationType;
    sourceMode?: GatewaySourceMode;
    sourceRef?: string;
    payload?: GatewayPrisma.JsonObject;
    reason?: string;
    requestedBy: string;
}

const GATEWAY_RELEASE_LOG_LEVELS = ['INFO', 'OUTPUT', 'ERROR'] as const;
export type GatewayReleaseLogLevel = (typeof GATEWAY_RELEASE_LOG_LEVELS)[number];

export interface GatewayReleaseLogRecord {
    cursor: string;
    operationId: string;
    level: GatewayReleaseLogLevel;
    phase: string;
    message: string;
    createdAt: string;
}

export interface GatewayReleaseLogInput {
    level: GatewayReleaseLogLevel;
    phase: string;
    message: string;
}

export interface GatewayReleaseRepository {
    getState(): Promise<GatewayReleaseStateRecord>;
    listOperations(limit?: number): Promise<GatewayReleaseOperationRecord[]>;
    getOperation(id: string): Promise<GatewayReleaseOperationRecord | null>;
    listOperationLogs(id: string, afterCursor?: string, limit?: number): Promise<GatewayReleaseLogRecord[]>;
    appendOperationLog(id: string, input: GatewayReleaseLogInput): Promise<GatewayReleaseLogRecord>;
    createOperation(input: GatewayReleaseOperationCreateInput): Promise<GatewayReleaseOperationRecord>;
    claimNextOperation(
        now: Date,
        lease: { ownerId: string; durationMs: number }
    ): Promise<GatewayReleaseOperationRecord | null>;
    renewOperationLease(id: string, ownerId: string, now: Date, durationMs: number): Promise<boolean>;
    pinOperationResolvedCommit(id: string, ownerId: string, resolvedCommitSha: string): Promise<boolean>;
    completeOperation(
        id: string,
        status: Extract<GatewayOperationStatus, 'SUCCEEDED' | 'FAILED'>,
        fields: { resolvedCommitSha?: string | null; error?: string | null },
        leaseOwner: string
    ): Promise<GatewayReleaseOperationRecord>;
    publishRelease(
        operationId: string,
        leaseOwner: string,
        release: { commitSha: string; workspace: string; previousCommitSha?: string; previousWorkspace?: string }
    ): Promise<GatewayReleaseStateRecord>;
    recordStateError(detail: string): Promise<void>;
    cancelOperation(id: string): Promise<boolean>;
    retryOperation(id: string, requestedBy: string): Promise<GatewayReleaseOperationRecord | null>;
}

const toIso = (value: Date | null): string | undefined => (value ? value.toISOString() : undefined);

const mapState = (row: {
    id: string;
    activeCommitSha: string | null;
    activeWorkspace: string | null;
    previousCommitSha: string | null;
    previousWorkspace: string | null;
    lastSuccessfulAt: Date | null;
    lastError: string | null;
    updatedAt: Date;
}): GatewayReleaseStateRecord => ({
    id: row.id,
    activeCommitSha: row.activeCommitSha ?? undefined,
    activeWorkspace: row.activeWorkspace ?? undefined,
    previousCommitSha: row.previousCommitSha ?? undefined,
    previousWorkspace: row.previousWorkspace ?? undefined,
    lastSuccessfulAt: toIso(row.lastSuccessfulAt),
    lastError: row.lastError ?? undefined,
    updatedAt: row.updatedAt.toISOString(),
});

const mapOperation = (row: {
    id: string;
    type: GatewayReleaseOperationType;
    status: GatewayOperationStatus;
    sourceMode: GatewaySourceMode | null;
    sourceRef: string | null;
    resolvedCommitSha: string | null;
    payload: GatewayPrisma.JsonValue;
    reason: string | null;
    requestedBy: string;
    startedAt: Date | null;
    completedAt: Date | null;
    error: string | null;
    leaseOwner: string | null;
    leaseUntil: Date | null;
    heartbeatAt: Date | null;
    attempts: number;
    createdAt: Date;
    updatedAt: Date;
}): GatewayReleaseOperationRecord => ({
    id: row.id,
    type: row.type,
    status: row.status,
    sourceMode: row.sourceMode ?? undefined,
    sourceRef: row.sourceRef ?? undefined,
    resolvedCommitSha: row.resolvedCommitSha ?? undefined,
    payload: (row.payload ?? {}) as GatewayPrisma.JsonObject,
    reason: row.reason ?? undefined,
    requestedBy: row.requestedBy,
    startedAt: toIso(row.startedAt),
    completedAt: toIso(row.completedAt),
    error: row.error ?? undefined,
    leaseOwner: row.leaseOwner ?? undefined,
    leaseUntil: toIso(row.leaseUntil),
    heartbeatAt: toIso(row.heartbeatAt),
    attempts: row.attempts,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
});

const mapLog = (row: {
    id: bigint;
    operationId: string;
    level: string;
    phase: string;
    message: string;
    createdAt: Date;
}): GatewayReleaseLogRecord => ({
    cursor: row.id.toString(),
    operationId: row.operationId,
    level: GATEWAY_RELEASE_LOG_LEVELS.includes(row.level as GatewayReleaseLogLevel)
        ? (row.level as GatewayReleaseLogLevel)
        : 'INFO',
    phase: row.phase,
    message: row.message,
    createdAt: row.createdAt.toISOString(),
});

export const createGatewayReleaseRepository = (prisma: GatewayPrismaClient): GatewayReleaseRepository => ({
    async getState() {
        const row = await prisma.gatewayReleaseState.upsert({
            where: { id: 'gateway' },
            create: { id: 'gateway' },
            update: {},
        });
        return mapState(row);
    },
    async listOperations(limit = 50) {
        const rows = await prisma.gatewayReleaseOperation.findMany({
            orderBy: { createdAt: 'desc' },
            take: Math.min(Math.max(limit, 1), 200),
        });
        return rows.map(mapOperation);
    },
    async getOperation(id) {
        const row = await prisma.gatewayReleaseOperation.findUnique({ where: { id } });
        return row ? mapOperation(row) : null;
    },
    async listOperationLogs(id, afterCursor, limit = 200) {
        const rows = await prisma.gatewayReleaseLog.findMany({
            where: {
                operationId: id,
                ...(afterCursor ? { id: { gt: BigInt(afterCursor) } } : {}),
            },
            orderBy: { id: 'asc' },
            take: Math.min(Math.max(limit, 1), 500),
        });
        return rows.map(mapLog);
    },
    async appendOperationLog(id, input) {
        const row = await prisma.$transaction(async (tx) => {
            await tx.$queryRaw<Array<{ id: string }>>`
                SELECT "id" FROM "gateway_release_operation" WHERE "id" = ${id} FOR UPDATE
            `;
            return tx.gatewayReleaseLog.create({
                data: {
                    operationId: id,
                    level: input.level,
                    phase: input.phase.slice(0, 64),
                    message: input.message.slice(0, 4_000),
                },
            });
        });
        return mapLog(row);
    },
    async createOperation(input) {
        const row = await prisma.gatewayReleaseOperation.create({
            data: {
                type: input.type,
                sourceMode: input.sourceMode,
                sourceRef: input.sourceRef,
                payload: input.payload ?? {},
                reason: input.reason,
                requestedBy: input.requestedBy,
            },
        });
        return mapOperation(row);
    },
    async claimNextOperation(now, lease) {
        const row = await prisma.$transaction(async (tx) => {
            await tx.$queryRaw<Array<{ lock_result: string }>>`
                SELECT pg_advisory_xact_lock(
                    hashtextextended(${CONTROL_PLANE_OPERATION_CLAIM_LOCK}, 0)
                )::text AS lock_result
            `;
            const runningProfileOperation = await tx.gatewayOperation.findFirst({
                where: { status: 'RUNNING' },
                select: { id: true },
            });
            if (runningProfileOperation) {
                return null;
            }
            const staleBefore = new Date(now.getTime() - lease.durationMs);
            const running = await tx.gatewayReleaseOperation.findFirst({
                where: { status: 'RUNNING' },
                orderBy: { createdAt: 'asc' },
            });
            const runningIsStale = Boolean(
                running &&
                ((running.leaseUntil && running.leaseUntil < now) ||
                    (!running.leaseUntil && running.startedAt && running.startedAt <= staleBefore))
            );
            if (running && !runningIsStale) {
                return null;
            }
            const candidate =
                running ??
                (await tx.gatewayReleaseOperation.findFirst({
                    where: { status: 'QUEUED' },
                    orderBy: { createdAt: 'asc' },
                }));
            if (!candidate) {
                return null;
            }
            const claimed = await tx.gatewayReleaseOperation.updateMany({
                where:
                    candidate.status === 'QUEUED'
                        ? { id: candidate.id, status: 'QUEUED' }
                        : { id: candidate.id, status: 'RUNNING', leaseUntil: candidate.leaseUntil },
                data: {
                    status: 'RUNNING',
                    startedAt: candidate.startedAt ?? now,
                    completedAt: null,
                    error: null,
                    leaseOwner: lease.ownerId,
                    leaseUntil: new Date(now.getTime() + lease.durationMs),
                    heartbeatAt: now,
                    attempts: { increment: 1 },
                },
            });
            return claimed.count === 1 ? tx.gatewayReleaseOperation.findUnique({ where: { id: candidate.id } }) : null;
        });
        return row ? mapOperation(row) : null;
    },
    async renewOperationLease(id, ownerId, now, durationMs) {
        const updated = await prisma.gatewayReleaseOperation.updateMany({
            where: { id, status: 'RUNNING', leaseOwner: ownerId },
            data: {
                leaseUntil: new Date(now.getTime() + durationMs),
                heartbeatAt: now,
            },
        });
        return updated.count === 1;
    },
    async pinOperationResolvedCommit(id, ownerId, resolvedCommitSha) {
        const updated = await prisma.gatewayReleaseOperation.updateMany({
            where: {
                id,
                status: 'RUNNING',
                leaseOwner: ownerId,
                OR: [{ resolvedCommitSha: null }, { resolvedCommitSha }],
            },
            data: { resolvedCommitSha },
        });
        return updated.count === 1;
    },
    async completeOperation(id, status, fields, leaseOwner) {
        const updated = await prisma.gatewayReleaseOperation.updateMany({
            where: { id, status: 'RUNNING', leaseOwner },
            data: {
                status,
                completedAt: new Date(),
                resolvedCommitSha: fields.resolvedCommitSha,
                error: fields.error,
                leaseOwner: null,
                leaseUntil: null,
                heartbeatAt: null,
            },
        });
        if (updated.count !== 1) {
            throw new Error(`Gateway release lease lost before completion: ${id}`);
        }
        return mapOperation(await prisma.gatewayReleaseOperation.findUniqueOrThrow({ where: { id } }));
    },
    async publishRelease(operationId, leaseOwner, release) {
        const row = await prisma.$transaction(async (tx) => {
            const owned = await tx.gatewayReleaseOperation.findFirst({
                where: { id: operationId, status: 'RUNNING', leaseOwner },
                select: { id: true },
            });
            if (!owned) {
                throw new Error(`Gateway release lease lost before publish: ${operationId}`);
            }
            return tx.gatewayReleaseState.upsert({
                where: { id: 'gateway' },
                create: {
                    id: 'gateway',
                    activeCommitSha: release.commitSha,
                    activeWorkspace: release.workspace,
                    previousCommitSha: release.previousCommitSha,
                    previousWorkspace: release.previousWorkspace,
                    lastSuccessfulAt: new Date(),
                    lastError: null,
                },
                update: {
                    activeCommitSha: release.commitSha,
                    activeWorkspace: release.workspace,
                    previousCommitSha: release.previousCommitSha,
                    previousWorkspace: release.previousWorkspace,
                    lastSuccessfulAt: new Date(),
                    lastError: null,
                },
            });
        });
        return mapState(row);
    },
    async recordStateError(detail) {
        await prisma.gatewayReleaseState.upsert({
            where: { id: 'gateway' },
            create: { id: 'gateway', lastError: detail },
            update: { lastError: detail },
        });
    },
    async cancelOperation(id) {
        const count = await prisma.$transaction(async (tx) => {
            const rows = await tx.$queryRaw<Array<{ status: GatewayOperationStatus }>>`
                SELECT "status"
                FROM "gateway_release_operation"
                WHERE "id" = ${id}
                FOR UPDATE
            `;
            const operation = rows[0];
            if (!operation || (operation.status !== 'QUEUED' && operation.status !== 'RUNNING')) return 0;
            if (operation.status === 'RUNNING') {
                const latestLog = await tx.gatewayReleaseLog.findFirst({
                    where: { operationId: id },
                    orderBy: { id: 'desc' },
                    select: { phase: true },
                });
                if (!latestLog || !['claim', 'resolve', 'workspace', 'build'].includes(latestLog.phase)) return 0;
            }
            const updated = await tx.gatewayReleaseOperation.updateMany({
                where: { id, status: operation.status },
                data: {
                    status: 'CANCELLED',
                    completedAt: new Date(),
                    leaseOwner: null,
                    leaseUntil: null,
                    heartbeatAt: null,
                },
            });
            if (updated.count !== 1) return 0;
            await tx.gatewayReleaseLog.create({
                data: {
                    operationId: id,
                    level: 'INFO',
                    phase: 'cancel',
                    message:
                        operation.status === 'RUNNING'
                            ? '실행 중인 Gateway 빌드를 중단했습니다. 현재 active release는 유지됩니다.'
                            : '대기 중인 Gateway 릴리스를 취소했습니다.',
                },
            });
            return 1;
        });
        return count === 1;
    },
    async retryOperation(id, requestedBy) {
        const row = await prisma.$transaction(async (tx) => {
            const previous = await tx.gatewayReleaseOperation.findUnique({ where: { id } });
            if (!previous || (previous.status !== 'FAILED' && previous.status !== 'CANCELLED')) {
                return null;
            }
            return tx.gatewayReleaseOperation.create({
                data: {
                    type: previous.type,
                    sourceMode: previous.resolvedCommitSha ? 'COMMIT' : previous.sourceMode,
                    sourceRef: previous.resolvedCommitSha ?? previous.sourceRef,
                    payload: previous.payload as GatewayPrisma.JsonObject,
                    reason: previous.reason,
                    requestedBy,
                },
            });
        });
        return row ? mapOperation(row) : null;
    },
});
