<template>
	<div class="data-source-page">
		<div class="data-source-page__toolbar">
			<el-button type="primary" @click="handleAdd">新增数据源</el-button>
			<el-input
				v-model="keyword"
				placeholder="搜索名称、库名、Host"
				clearable
				style="width: 260px"
			>
				<template #prefix>
					<el-icon><Search /></el-icon>
				</template>
			</el-input>
			<el-button :icon="Refresh" @click="loadData">刷新</el-button>
			<el-alert
				title="连接信息仅管理员可见，Agent 只会看到授权后的数据源名称和数据库名。"
				type="info"
				show-icon
				:closable="false"
				class="toolbar-alert"
			/>
		</div>

		<el-table v-loading="loading" :data="filteredRows" border stripe class="data-source-page__table">
			<el-table-column prop="name" label="名称" min-width="150" show-overflow-tooltip />
			<el-table-column prop="label" label="显示名" min-width="140" show-overflow-tooltip />
			<el-table-column prop="database" label="数据库" min-width="140" show-overflow-tooltip />
			<el-table-column label="连接" min-width="230" show-overflow-tooltip>
				<template #default="{ row }">
					<span>{{ row.host }}:{{ row.port }}</span>
					<span class="muted"> / {{ row.username }}</span>
				</template>
			</el-table-column>
			<el-table-column label="授权 Agent" min-width="180">
				<template #default="{ row }">
					<template v-if="normalizeNumberList(row.allowedAgentIds).length">
						<el-tag
							v-for="id in normalizeNumberList(row.allowedAgentIds).slice(0, 3)"
							:key="id"
							size="small"
							class="tag-gap"
						>
							{{ agentLabel(id) }}
						</el-tag>
						<span v-if="normalizeNumberList(row.allowedAgentIds).length > 3" class="muted">
							+{{ normalizeNumberList(row.allowedAgentIds).length - 3 }}
						</span>
					</template>
					<span v-else class="muted">未授权</span>
				</template>
			</el-table-column>
			<el-table-column label="策略" min-width="220">
				<template #default="{ row }">
					<el-tag size="small" type="info" class="tag-gap">
						表 {{ normalizeStringList(row.extra?.allowedTables).length }}
					</el-tag>
					<el-tag size="small" type="warning" class="tag-gap">
						脱敏 {{ Object.keys(row.extra?.maskedColumns || {}).length }}
					</el-tag>
					<el-tag size="small" type="success">Max {{ row.extra?.maxRows || 200 }}</el-tag>
				</template>
			</el-table-column>
			<el-table-column label="状态" width="100" align="center">
				<template #default="{ row }">
					<el-switch
						:model-value="row.status === 1"
						inline-prompt
						active-text="启用"
						inactive-text="禁用"
						@change="value => handleToggle(row, value)"
					/>
				</template>
			</el-table-column>
			<el-table-column label="密码" width="90" align="center">
				<template #default="{ row }">
					<el-tag :type="row.hasPassword ? 'success' : 'danger'" size="small">
						{{ row.hasPassword ? '已配置' : '未配置' }}
					</el-tag>
				</template>
			</el-table-column>
			<el-table-column label="操作" width="220" fixed="right" align="center">
				<template #default="{ row }">
					<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
					<el-button link type="success" size="small" :loading="testingId === row.id" @click="handleTest(row)">
						测试
					</el-button>
					<el-popconfirm title="确定删除该数据源？" @confirm="handleDelete(row)">
						<template #reference>
							<el-button link type="danger" size="small">删除</el-button>
						</template>
					</el-popconfirm>
				</template>
			</el-table-column>
		</el-table>

		<el-drawer v-model="drawer.visible" :title="drawer.isEdit ? '编辑数据源' : '新增数据源'" size="720px">
			<el-form ref="formRef" :model="form" :rules="rules" label-width="112px" label-position="left">
				<div class="section-title">连接信息</div>
				<el-form-item label="唯一名称" prop="name">
					<el-input v-model="form.name" placeholder="sales_prod" :disabled="drawer.isEdit" />
				</el-form-item>
				<el-form-item label="显示名称">
					<el-input v-model="form.label" placeholder="销售生产库" />
				</el-form-item>
				<el-row :gutter="12">
					<el-col :span="16">
						<el-form-item label="Host" prop="host">
							<el-input v-model="form.host" placeholder="127.0.0.1" />
						</el-form-item>
					</el-col>
					<el-col :span="8">
						<el-form-item label="端口" prop="port">
							<el-input v-model="form.port" placeholder="3306" maxlength="5" />
						</el-form-item>
					</el-col>
				</el-row>
				<el-row :gutter="12">
					<el-col :span="12">
						<el-form-item label="数据库" prop="database">
							<el-input v-model="form.database" placeholder="sales" />
						</el-form-item>
					</el-col>
					<el-col :span="12">
						<el-form-item label="用户名" prop="username">
							<el-input v-model="form.username" placeholder="readonly_user" />
						</el-form-item>
					</el-col>
				</el-row>
				<el-form-item label="密码" :prop="drawer.isEdit ? undefined : 'password'">
					<el-input
						v-model="form.password"
						show-password
						:placeholder="drawer.isEdit ? '留空表示不修改密码' : '请输入连接密码'"
					/>
				</el-form-item>
				<el-form-item label="SSL">
					<el-switch v-model="form.ssl" />
				</el-form-item>

				<div class="section-title">授权与安全策略</div>
				<el-form-item label="授权 Agent">
					<el-select
						v-model="form.allowedAgentIds"
						multiple
						filterable
						clearable
						placeholder="选择允许使用该数据源的 Agent"
						style="width: 100%"
					>
						<el-option v-for="item in agentOptions" :key="item.id" :label="item.label" :value="item.id" />
					</el-select>
				</el-form-item>
				<el-form-item label="表白名单">
					<el-input
						v-model="form.allowedTablesText"
						type="textarea"
						:rows="3"
						placeholder="一行一个表名，如：orders&#10;customers"
					/>
				</el-form-item>
				<el-form-item label="表黑名单">
					<el-input
						v-model="form.blockedTablesText"
						type="textarea"
						:rows="2"
						placeholder="一行一个表名，可选"
					/>
				</el-form-item>
				<el-form-item label="脱敏列">
					<div class="masked-editor">
						<div v-for="(item, index) in form.maskedColumns" :key="index" class="masked-row">
							<el-input v-model="item.column" placeholder="customers.phone" />
							<el-select v-model="item.mode" style="width: 120px">
								<el-option label="部分隐藏" value="partial" />
								<el-option label="哈希" value="hash" />
								<el-option label="脱敏" value="redact" />
							</el-select>
							<el-button :icon="Delete" circle @click="form.maskedColumns.splice(index, 1)" />
						</div>
						<el-button :icon="Plus" @click="form.maskedColumns.push({ column: '', mode: 'partial' })">
							添加脱敏列
						</el-button>
					</div>
				</el-form-item>
				<el-row :gutter="12">
					<el-col :span="8">
						<el-form-item label="Schema 可见性">
							<el-select v-model="form.schemaVisibility" style="width: 100%">
								<el-option label="仅白名单表" value="allowed-only" />
								<el-option label="全表名可见" value="all-names-only" />
							</el-select>
						</el-form-item>
					</el-col>
					<el-col :span="8">
						<el-form-item label="最大行数">
							<el-input-number v-model="form.maxRows" :min="1" :max="1000" style="width: 100%" />
						</el-form-item>
					</el-col>
					<el-col :span="8">
						<el-form-item label="最大 JOIN 表">
							<el-input-number v-model="form.maxJoinTables" :min="1" :max="6" style="width: 100%" />
						</el-form-item>
					</el-col>
				</el-row>
				<el-row :gutter="12">
					<el-col :span="12">
						<el-form-item label="查询超时 ms">
							<el-input-number v-model="form.queryTimeoutMs" :min="1000" :max="30000" :step="1000" style="width: 100%" />
						</el-form-item>
					</el-col>
					<el-col :span="12">
						<el-form-item label="连接超时 ms">
							<el-input-number v-model="form.connectTimeout" :min="1000" :max="30000" :step="1000" style="width: 100%" />
						</el-form-item>
					</el-col>
				</el-row>
			</el-form>

			<template #footer>
				<el-button @click="drawer.visible = false">取消</el-button>
				<el-button :loading="testingForm" @click="handleTestForm">测试连接</el-button>
				<el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
			</template>
		</el-drawer>
	</div>
</template>

<script lang="ts" setup>
defineOptions({ name: 'sys-data-source' });

import { computed, onMounted, reactive, ref } from 'vue';
import { Delete, Plus, Refresh, Search } from '@element-plus/icons-vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { useCool } from '/@/cool';

type MaskMode = 'partial' | 'hash' | 'redact';

interface AgentOption {
	id: number;
	label: string;
}

interface DataSourceForm {
	id: number | null;
	name: string;
	label: string;
	host: string;
	port: string;
	database: string;
	username: string;
	password: string;
	ssl: boolean;
	status: number;
	allowedAgentIds: number[];
	allowedTablesText: string;
	blockedTablesText: string;
	maskedColumns: Array<{ column: string; mode: MaskMode }>;
	schemaVisibility: 'allowed-only' | 'all-names-only';
	maxRows: number;
	maxJoinTables: number;
	queryTimeoutMs: number;
	connectTimeout: number;
}

const { service } = useCool();

const loading = ref(false);
const submitting = ref(false);
const testingForm = ref(false);
const testingId = ref<number | null>(null);
const keyword = ref('');
const rows = ref<any[]>([]);
const agentOptions = ref<AgentOption[]>([]);
const formRef = ref<FormInstance>();

const drawer = reactive({
	visible: false,
	isEdit: false
});

const form = reactive<DataSourceForm>(createEmptyForm());

const rules: FormRules = {
	name: [{ required: true, message: '请输入唯一名称', trigger: 'blur' }],
	host: [{ required: true, message: '请输入 Host', trigger: 'blur' }],
	port: [
		{ required: true, message: '请输入端口', trigger: 'blur' },
		{
			validator: (_rule, value, callback) => {
				const port = Number(String(value).trim());
				if (!Number.isInteger(port) || port < 1 || port > 65535) {
					callback(new Error('端口范围为 1-65535'));
					return;
				}
				callback();
			},
			trigger: 'blur'
		}
	],
	database: [{ required: true, message: '请输入数据库名', trigger: 'blur' }],
	username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
	password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
};

const filteredRows = computed(() => {
	const kw = keyword.value.trim().toLowerCase();
	if (!kw) return rows.value;
	return rows.value.filter(row =>
		[row.name, row.label, row.database, row.host, row.username]
			.filter(Boolean)
			.some(value => String(value).toLowerCase().includes(kw))
	);
});

function createEmptyForm(): DataSourceForm {
	return {
		id: null,
		name: '',
		label: '',
		host: '',
		port: '3306',
		database: '',
		username: '',
		password: '',
		ssl: false,
		status: 1,
		allowedAgentIds: [],
		allowedTablesText: '',
		blockedTablesText: '',
		maskedColumns: [],
		schemaVisibility: 'allowed-only',
		maxRows: 200,
		maxJoinTables: 4,
		queryTimeoutMs: 10000,
		connectTimeout: 10000
	};
}

function assignForm(next: DataSourceForm) {
	Object.assign(form, next);
}

function normalizeStringList(value: unknown): string[] {
	if (!Array.isArray(value)) return [];
	return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);
}

function normalizeNumberList(value: unknown): number[] {
	if (!Array.isArray(value)) return [];
	return value.map(Number).filter(Number.isFinite).map(item => Math.trunc(item));
}

function splitLines(value: string): string[] {
	return value
		.split(/[\n,，]/)
		.map(item => item.trim())
		.filter(Boolean);
}

function agentLabel(id: number) {
	return agentOptions.value.find(item => item.id === id)?.label || `Agent #${id}`;
}

function rowToForm(row: any): DataSourceForm {
	const extra = row.extra || {};
	const maskedColumns = Object.entries(extra.maskedColumns || {}).map(([column, mode]) => ({
		column,
		mode: mode === 'hash' || mode === 'redact' ? mode : 'partial'
	})) as Array<{ column: string; mode: MaskMode }>;

	return {
		id: row.id,
		name: row.name || '',
		label: row.label || '',
		host: row.host || '',
		port: String(row.port || 3306),
		database: row.database || '',
		username: row.username || '',
		password: '',
		ssl: !!extra.ssl,
		status: row.status ?? 1,
		allowedAgentIds: normalizeNumberList(row.allowedAgentIds),
		allowedTablesText: normalizeStringList(extra.allowedTables).join('\n'),
		blockedTablesText: normalizeStringList(extra.blockedTables).join('\n'),
		maskedColumns,
		schemaVisibility: extra.schemaVisibility === 'all-names-only' ? 'all-names-only' : 'allowed-only',
		maxRows: Number(extra.maxRows || 200),
		maxJoinTables: Number(extra.maxJoinTables || 4),
		queryTimeoutMs: Number(extra.queryTimeoutMs || 10000),
		connectTimeout: Number(extra.connectTimeout || 10000)
	};
}

function buildPayload() {
	const maskedColumns: Record<string, MaskMode> = {};
	for (const item of form.maskedColumns) {
		const column = item.column.trim();
		if (column) {
			maskedColumns[column] = item.mode;
		}
	}

	const payload: any = {
		id: form.id || undefined,
		name: form.name.trim(),
		label: form.label.trim() || null,
		host: form.host.trim(),
		port: Number(form.port),
		database: form.database.trim(),
		username: form.username.trim(),
		status: form.status,
		allowedAgentIds: form.allowedAgentIds,
		extra: {
			ssl: form.ssl,
			allowedTables: splitLines(form.allowedTablesText),
			blockedTables: splitLines(form.blockedTablesText),
			maskedColumns,
			schemaVisibility: form.schemaVisibility,
			maxRows: form.maxRows,
			maxJoinTables: form.maxJoinTables,
			queryTimeoutMs: form.queryTimeoutMs,
			connectTimeout: form.connectTimeout
		}
	};

	if (form.password.trim()) {
		payload.password = form.password;
	}

	return payload;
}

async function loadAgents() {
	try {
		const res = await service.request({
			url: '/admin/netaclaw/agent/options',
			method: 'POST'
		});
		const list = Array.isArray(res) ? res : [];
		agentOptions.value = list
			.map((item: any) => ({
				id: Number(item.id),
				label: item.label || item.name || `Agent #${item.id}`
			}))
			.filter(item => Number.isFinite(item.id));
	} catch {
		agentOptions.value = [];
	}
}

async function loadData() {
	loading.value = true;
	try {
		const res = await service.request({
			url: '/admin/netaclaw/data-source/list'
		});
		rows.value = Array.isArray(res) ? res : [];
	} catch {
		ElMessage.error('加载数据源失败');
	} finally {
		loading.value = false;
	}
}

function handleAdd() {
	drawer.isEdit = false;
	assignForm(createEmptyForm());
	formRef.value?.clearValidate();
	drawer.visible = true;
}

function handleEdit(row: any) {
	drawer.isEdit = true;
	assignForm(rowToForm(row));
	formRef.value?.clearValidate();
	drawer.visible = true;
}

async function handleSubmit() {
	const valid = await formRef.value?.validate().catch(() => false);
	if (!valid) return;

	submitting.value = true;
	try {
		await service.request({
			url: '/admin/netaclaw/data-source/save',
			method: 'POST',
			data: buildPayload()
		});
		ElMessage.success('保存成功');
		drawer.visible = false;
		await loadData();
	} catch {
		ElMessage.error('保存失败');
	} finally {
		submitting.value = false;
	}
}

async function handleTestForm() {
	const valid = await formRef.value?.validate().catch(() => false);
	if (!valid) return;

	testingForm.value = true;
	try {
		await service.request({
			url: '/admin/netaclaw/data-source/test',
			method: 'POST',
			data: buildPayload()
		});
		ElMessage.success('连接成功');
	} catch (error: any) {
		ElMessage.error(error?.message || '连接失败');
	} finally {
		testingForm.value = false;
	}
}

async function handleTest(row: any) {
	testingId.value = row.id;
	try {
		await service.request({
			url: '/admin/netaclaw/data-source/test',
			method: 'POST',
			data: { id: row.id }
		});
		ElMessage.success('连接成功');
	} catch (error: any) {
		ElMessage.error(error?.message || '连接失败');
	} finally {
		testingId.value = null;
	}
}

async function handleDelete(row: any) {
	try {
		await service.request({
			url: '/admin/netaclaw/data-source/delete',
			method: 'POST',
			data: { id: row.id }
		});
		ElMessage.success('删除成功');
		await loadData();
	} catch {
		ElMessage.error('删除失败');
	}
}

async function handleToggle(row: any, value: string | number | boolean) {
	try {
		await service.request({
			url: '/admin/netaclaw/data-source/save',
			method: 'POST',
			data: { id: row.id, status: value === true ? 1 : 0 }
		});
		row.status = value === true ? 1 : 0;
		ElMessage.success(row.status === 1 ? '已启用' : '已禁用');
	} catch {
		ElMessage.error('状态更新失败');
	}
}

onMounted(() => {
	loadAgents();
	loadData();
});
</script>

<style lang="scss" scoped>
.data-source-page {
	height: 100%;
	display: flex;
	flex-direction: column;
	padding: 15px;
	box-sizing: border-box;

	&__toolbar {
		display: flex;
		align-items: center;
		gap: 10px;
		padding: 15px;
		margin-bottom: 12px;
		background-color: var(--el-bg-color);
		border-radius: 6px;
		flex-wrap: wrap;
	}

	&__table {
		flex: 1;
	}
}

.toolbar-alert {
	width: auto;
	min-width: 360px;
	flex: 1;
}

.muted {
	color: var(--el-text-color-secondary);
}

.tag-gap {
	margin-right: 4px;
	margin-bottom: 4px;
}

.section-title {
	margin: 4px 0 14px;
	padding-left: 10px;
	border-left: 3px solid var(--el-color-primary);
	font-weight: 600;
	color: var(--el-text-color-primary);
}

.masked-editor {
	width: 100%;
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.masked-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 120px 32px;
	gap: 8px;
	align-items: center;
}
</style>
