<template>
  <div class="content-view">
    <div class="table-head-tool">
      <Form ref="formInline" :model="param.seachOption" inline :label-width="80">
        <FormItem>
          <Button type="primary" @click="openEdit(null)">新增套餐</Button>
        </FormItem>
        <FormItem label="条件">
          <Select v-model="param.seachOption.key" style="width: 140px">
            <Option value="plan_code">编码</Option>
            <Option value="plan_name">名称</Option>
            <Option value="status">状态</Option>
          </Select>
          <Input
            v-model="param.seachOption.value"
            class="ml10"
            style="width: 220px"
            search
            @on-search="load(1)"
          />
        </FormItem>
        <FormItem>
          <Button type="primary" @click="load(1)">查询</Button>
          <Dropdown trigger="click" class="ml10" @on-click="on_toolbar_more">
            <Button type="default">更多 <Icon type="ios-arrow-down" /></Button>
            <DropdownMenu slot="list">
              <DropdownItem name="export">导出 CSV</DropdownItem>
            </DropdownMenu>
          </Dropdown>
        </FormItem>
      </Form>
    </div>

    <div class="table-body">
      <Table :columns="columns" :data="rows" border stripe />
      <div class="table-page-bar">
        <Page
          :total="total"
          :current="param.pageOption.page"
          :page-size="param.pageOption.pageSize"
          show-total
          @on-change="onPage"
          @on-page-size-change="onSize"
        />
      </div>
    </div>

    <Modal v-model="modal" :title="form.id ? '编辑套餐' : '新增套餐'" width="900" :loading="saving" @on-ok="save">
      <Form ref="formRef" :model="form" :rules="rules" :label-width="120">
        <FormItem label="套餐编码" prop="plan_code">
          <Input v-model="form.plan_code" :disabled="!!form.id" />
        </FormItem>
        <FormItem label="套餐名称" prop="plan_name">
          <Input v-model="form.plan_name" />
        </FormItem>
        <FormItem label="月费">
          <Input v-model="form.monthly_price" type="number" />
        </FormItem>
        <FormItem label="授权费">
          <Input v-model="form.auth_fee" type="number" />
        </FormItem>
        <FormItem label="账号上限">
          <Input v-model="form.account_limit" type="number" />
        </FormItem>
        <FormItem label="活跃上限">
          <Input v-model="form.active_user_limit" type="number" />
        </FormItem>
        <FormItem label="消息额度">
          <Input v-model="form.msg_quota" type="number" />
        </FormItem>
        <FormItem label="群发额度">
          <Input v-model="form.mass_quota" type="number" />
        </FormItem>
        <FormItem label="好友额度">
          <Input v-model="form.friend_quota" type="number" />
        </FormItem>
        <FormItem label="朋友圈额度">
          <Input v-model="form.sns_quota" type="number" />
        </FormItem>
        <FormItem label="API调用上限">
          <Input v-model="form.api_call_quota" type="number" placeholder="0=不限制" />
        </FormItem>
        <FormItem label="功能点 JSON">
          <Input v-model="featuresText" type="textarea" :rows="4" placeholder='如 {"msg":true} 或 ["msg","mass"]' />
        </FormItem>
        <FormItem label="API 权限">
          <RadioGroup v-model="api_permission_mode">
            <Radio label="all">不限制（Swagger 内转发接口均可调用）</Radio>
            <Radio label="whitelist">仅允许勾选的接口路径（白名单）</Radio>
          </RadioGroup>
          <p class="form-hint">白名单与鉴权路径一致（如 /user/GetProfile），目录来自 swagger.json。</p>
        </FormItem>
        <FormItem v-if="api_permission_mode === 'whitelist'" label=" ">
          <Alert v-if="selected_api_paths.length === 0" type="warning" show-icon class="mb12">
            未勾选任何接口时，保存后该套餐将无法调用任何转发 API。
          </Alert>
          <div v-if="proxy_catalog.tags.length === 0" class="text-muted">暂无接口目录（请检查 swagger），可联系后台配置。</div>
          <div v-else class="api-catalog-wrap">
            <div class="api-catalog-toolbar">
              <Button type="default" size="small" @click="select_all_catalog">全选</Button>
              <Button type="default" size="small" class="ml8" @click="clear_all_catalog">全不选</Button>
              <span class="api-catalog-stats ml10">已选 {{ selected_api_paths.length }} / {{ catalog_path_count }}</span>
            </div>
            <Collapse v-model="api_collapse">
              <Panel v-for="tag in proxy_catalog.tags" :key="tag" :name="tag">
                {{ tag }}（{{ count_selected_in_tag(tag) }}/{{ (proxy_catalog.groups[tag] || []).length }}）
                <div slot="content">
                  <div class="panel-actions mb8">
                    <Button type="text" size="small" @click="select_all_in_tag(tag)">本组全选</Button>
                    <Button type="text" size="small" @click="clear_tag(tag)">本组清空</Button>
                  </div>
                  <div class="api-check-grid">
                    <Checkbox
                      v-for="it in (proxy_catalog.groups[tag] || [])"
                      :key="it.path"
                      :value="selected_api_paths.includes(it.path)"
                      class="api-check-item"
                      @on-change="(v) => set_api_path_checked(it.path, v)"
                    >
                      <span class="api-path-text">{{ it.path }}</span>
                      <span class="api-meta">{{ it.methods.join(', ') }}</span>
                      <span v-if="it.summary" class="api-summary">{{ it.summary }}</span>
                    </Checkbox>
                  </div>
                </div>
              </Panel>
            </Collapse>
            <div v-if="orphan_api_paths.length" class="orphan-apis mt12">
              <p class="sub-label">以下路径不在当前目录中（仍会计入白名单，可点 × 移除）</p>
              <Tag
                v-for="p in orphan_api_paths"
                :key="p"
                closable
                @on-close="() => remove_orphan_path(p)"
              >{{ p }}</Tag>
            </div>
          </div>
        </FormItem>
        <FormItem label="状态" prop="status">
          <Select v-model="form.status" style="width: 100%">
            <Option value="active">上线</Option>
            <Option value="inactive">下线</Option>
          </Select>
        </FormItem>
      </Form>
    </Modal>
  </div>
</template>

<script>
import planServer from '@/api/subscription/plan_server.js'
import { downloadCsvFromRows } from '@/utils/csvExport.js'

export default {
  name: 'SubscriptionPlans',
  data() {
    return {
      rows: [],
      total: 0,
      param: {
        seachOption: { key: 'plan_code', value: '' },
        pageOption: { page: 1, pageSize: 20, total: 0 },
      },
      modal: false,
      saving: false,
      form: {},
      featuresText: '{}',
      api_permission_mode: 'all',
      selected_api_paths: [],
      proxy_catalog: { items: [], groups: {}, tags: [] },
      api_collapse: [],
      rules: {
        plan_code: [{ required: true, message: '必填', trigger: 'blur' }],
        plan_name: [{ required: true, message: '必填', trigger: 'blur' }],
        status: [{ required: true, message: '必选', trigger: 'change' }],
      },
    }
  },
  computed: {
    catalog_path_set() {
      return new Set((this.proxy_catalog.items || []).map((x) => x.path))
    },
    catalog_path_count() {
      return (this.proxy_catalog.items || []).length
    },
    orphan_api_paths() {
      return this.selected_api_paths.filter((p) => !this.catalog_path_set.has(p))
    },
    columns() {
      return [
        { title: 'ID', key: 'id', width: 70 },
        { title: '编码', key: 'plan_code', width: 120 },
        { title: '名称', key: 'plan_name', minWidth: 140 },
        { title: '月费', key: 'monthly_price', width: 90 },
        { title: 'API调用上限', key: 'api_call_quota', width: 120, render: (h, p) => h('span', p.row.api_call_quota > 0 ? p.row.api_call_quota : '不限') },
        {
          title: 'API权限',
          key: 'api_perm',
          width: 108,
          render: (h, p) => {
            const a = p.row.allowed_apis
            if (a == null || a === '') return h('span', '不限制')
            let list = a
            if (typeof list === 'string') {
              try {
                list = JSON.parse(list)
              } catch (e) {
                return h('span', '—')
              }
            }
            if (!Array.isArray(list)) return h('span', '—')
            if (list.length === 0) return h('span', { class: 'text-danger' }, '全禁')
            return h('span', `${list.length} 项`)
          },
        },
        { title: '状态', key: 'status', width: 90 },
        {
          title: '操作',
          key: 'a',
          width: 180,
          render: (h, p) =>
            h('div', [
              h('Button', { props: { type: 'info', size: 'small' }, on: { click: () => this.openEdit(p.row) } }, '编辑'),
              h(
                'Dropdown',
                {
                  props: { trigger: 'click', transfer: true },
                  class: { ml8: true },
                  on: {
                    'on-click': (name) => {
                      if (name === 'toggle') this.toggle(p.row)
                      else if (name === 'del') this.doDel(p.row)
                    },
                  },
                },
                [
                  h(
                    'Button',
                    { props: { size: 'small' } },
                    ['更多 ', h('Icon', { props: { type: 'ios-arrow-down' } })]
                  ),
                  h(
                    'DropdownMenu',
                    { slot: 'list' },
                    [
                      h('DropdownItem', { props: { name: 'toggle' } }, '上下线'),
                      h('DropdownItem', { props: { name: 'del' } }, '删除'),
                    ]
                  ),
                ]
              ),
            ]),
        },
      ]
    },
  },
  mounted() {
    this.load(1)
    this.load_proxy_catalog()
  },
  methods: {
    async load_proxy_catalog() {
      try {
        const res = await planServer.proxyApiCatalog()
        if (res && res.code === 0 && res.data) {
          this.proxy_catalog = {
            items: res.data.items || [],
            groups: res.data.groups || {},
            tags: res.data.tags || [],
          }
        }
      } catch (e) {
        this.$Message.error('加载转发接口目录失败')
      }
    },
    normalize_allowed_apis_raw(raw) {
      if (raw == null || raw === '') return []
      let list = raw
      if (typeof list === 'string') {
        try {
          list = JSON.parse(list)
        } catch (e) {
          return []
        }
      }
      return Array.isArray(list) ? [...list] : []
    },
    count_selected_in_tag(tag) {
      const paths = new Set((this.proxy_catalog.groups[tag] || []).map((x) => x.path))
      return this.selected_api_paths.filter((p) => paths.has(p)).length
    },
    set_api_path_checked(path, checked) {
      const set = new Set(this.selected_api_paths)
      if (checked) set.add(path)
      else set.delete(path)
      this.selected_api_paths = Array.from(set).sort((a, b) => a.localeCompare(b))
    },
    select_all_in_tag(tag) {
      const set = new Set(this.selected_api_paths)
      for (const it of this.proxy_catalog.groups[tag] || []) set.add(it.path)
      this.selected_api_paths = Array.from(set).sort((a, b) => a.localeCompare(b))
    },
    clear_tag(tag) {
      const rm = new Set((this.proxy_catalog.groups[tag] || []).map((x) => x.path))
      this.selected_api_paths = this.selected_api_paths.filter((p) => !rm.has(p))
    },
    select_all_catalog() {
      const set = new Set(this.selected_api_paths)
      for (const it of this.proxy_catalog.items || []) set.add(it.path)
      this.selected_api_paths = Array.from(set).sort((a, b) => a.localeCompare(b))
    },
    clear_all_catalog() {
      const keep = new Set(this.orphan_api_paths)
      this.selected_api_paths = Array.from(keep).sort((a, b) => a.localeCompare(b))
    },
    remove_orphan_path(path) {
      this.selected_api_paths = this.selected_api_paths.filter((p) => p !== path)
    },
    async load(page) {
      if (page) this.param.pageOption.page = page
      const res = await planServer.page({ param: this.param })
      if (res && res.code === 0) {
        this.rows = res.data.rows || []
        this.total = res.data.count || 0
      } else {
        this.$Message.error((res && res.message) || '加载失败')
      }
    },
    onPage(p) {
      this.param.pageOption.page = p
      this.load()
    },
    onSize(s) {
      this.param.pageOption.pageSize = s
      this.load(1)
    },
    openEdit(row) {
      if (row) {
        this.form = { ...row }
        this.featuresText =
          row.enabled_features == null
            ? ''
            : typeof row.enabled_features === 'string'
              ? row.enabled_features
              : JSON.stringify(row.enabled_features, null, 2)
        if (row.allowed_apis == null || row.allowed_apis === '') {
          this.api_permission_mode = 'all'
          this.selected_api_paths = []
        } else {
          this.api_permission_mode = 'whitelist'
          this.selected_api_paths = this.normalize_allowed_apis_raw(row.allowed_apis)
        }
      } else {
        this.form = {
          plan_code: '',
          plan_name: '',
          monthly_price: 0,
          auth_fee: 0,
          account_limit: 0,
          active_user_limit: 0,
          msg_quota: 0,
          mass_quota: 0,
          friend_quota: 0,
          sns_quota: 0,
          api_call_quota: 0,
          status: 'active',
        }
        this.featuresText = '{}'
        this.api_permission_mode = 'all'
        this.selected_api_paths = []
      }
      this.api_collapse = this.proxy_catalog.tags.length ? [...this.proxy_catalog.tags] : []
      this.modal = true
    },
    save() {
      this.saving = true
      this.$refs.formRef.validate(async (ok) => {
        if (!ok) {
          this.saving = false
          return
        }
        let enabled_features = null
        const t = (this.featuresText || '').trim()
        if (t) {
          try {
            enabled_features = JSON.parse(t)
          } catch (e) {
            this.$Message.error('功能点 JSON 格式错误')
            this.saving = false
            return
          }
        }
        let allowed_apis = null
        if (this.api_permission_mode === 'whitelist') {
          allowed_apis = [...this.selected_api_paths]
        }
        const payload = { ...this.form, enabled_features, allowed_apis }
        try {
          const res = this.form.id ? await planServer.edit(payload) : await planServer.add(payload)
          if (res && res.code === 0) {
            this.$Message.success('保存成功')
            this.modal = false
            this.load(1)
          } else {
            this.$Message.error((res && res.message) || '保存失败')
          }
        } finally {
          this.saving = false
        }
      })
    },
    on_toolbar_more(name) {
      if (name === 'export') this.doExport()
    },
    async toggle(row) {
      const res = await planServer.toggle({ id: row.id })
      if (res && res.code === 0) {
        this.$Message.success('状态已更新为 ' + (res.data && res.data.status))
        this.load()
      } else {
        this.$Message.error((res && res.message) || '失败')
      }
    },
    doDel(row) {
      this.$Modal.confirm({
        title: '删除套餐',
        content: '确认删除？若已被订阅引用可能失败。',
        onOk: async () => {
          const res = await planServer.del({ id: row.id })
          if (res && res.code === 0) {
            this.$Message.success('已删除')
            this.load(1)
          } else {
            this.$Message.error((res && res.message) || '删除失败')
          }
        },
      })
    },
    async doExport() {
      const res = await planServer.exportRows({ param: this.param })
      if (res && res.code === 0 && res.data && res.data.rows) {
        downloadCsvFromRows(res.data.rows, 'plans.csv')
        this.$Message.success('已导出')
      } else {
        this.$Message.error((res && res.message) || '导出失败')
      }
    },
  },
}
</script>

<style lang="less" scoped>
.content-view {
  width: 100%;
  max-width: 1720px;
  margin: 0 auto;
  padding: 26px 36px 36px;
  box-sizing: border-box;
}

.table-head-tool {
  margin-bottom: 16px;
}

.ml10 {
  margin-left: 10px;
}

.ml8 {
  margin-left: 8px;
}

.table-page-bar {
  margin-top: 12px;
  text-align: right;
}

.form-hint {
  color: #808695;
  font-size: 12px;
  margin-top: 6px;
}

.text-muted {
  color: #808695;
}

.text-danger {
  color: #ed4014;
}

.mb12 {
  margin-bottom: 12px;
}

.mt12 {
  margin-top: 12px;
}

.api-catalog-wrap {
  max-height: 420px;
  overflow: auto;
  padding: 12px;
  border: 1px solid #e8eaec;
  border-radius: 4px;
}

.api-catalog-toolbar {
  margin-bottom: 10px;
}

.api-catalog-stats {
  color: #808695;
  font-size: 12px;
}

.panel-actions .ivu-btn-text + .ivu-btn-text {
  margin-left: 4px;
}

.api-check-grid {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.api-path-text {
  font-weight: 500;
  margin-right: 8px;
}

.api-meta {
  color: #808695;
  font-size: 12px;
  margin-right: 8px;
}

.api-summary {
  color: #515a6e;
  font-size: 12px;
}

.sub-label {
  font-size: 12px;
  color: #808695;
  margin-bottom: 8px;
}

.orphan-apis .ivu-tag {
  margin: 0 8px 8px 0;
}
</style>
