import asyncio
import copy
import hashlib
import json
import pytz
import re
import time
import yaml
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Literal, Tuple, overload

from fastapi import HTTPException
from apscheduler.schedulers.asyncio import AsyncIOScheduler

from app.core.cache import cached
from app.core.config import settings
from app.log import logger
from app.utils.http import AsyncRequestUtils

from .base import _ClashRuleProviderBase as Crp
from .config import PluginConfig
from .helper.clashrulemanager import RuleItem, ClashRuleManager
from .helper.clashruleparser import ClashRuleParser, RoutingRuleType, Action, ClashRule
from .helper.configconverter import Converter
from .helper.utilsprovider import UtilsProvider
from .models import ProxyBase, TLSMixin, NetworkMixin, ProxyGroup, Proxy
from .models.api import RuleData, ClashApi, RuleProviderData, SubscriptionInfo, HostData
from .state import PluginState
from .store import PluginStore


class ClashRuleProviderService:

    def __init__(
            self, plugin_id: str,
            config: PluginConfig,
            state: PluginState,
            store: PluginStore,
            scheduler: Optional[AsyncIOScheduler] = None
    ):
        self.plugin_id = plugin_id
        self.config = config
        self.state = state
        self.store = store
        self.scheduler = scheduler

    def save_rules(self):
        self.store.save_data(Crp.KEY_TOP_RULES, self.state.top_rules_manager.export_rules())
        self.store.save_data(Crp.KEY_RULESET_RULES, self.state.ruleset_rules_manager.export_rules())

    def load_rules(self):
        def process_rules(raw_rules: List[str | dict[str, Any]], manager: ClashRuleManager, key: str):
            raw_rules = raw_rules or []
            rules = [self._upgrade_rule(r) if isinstance(r, str) else r for r in raw_rules]
            manager.import_rules(rules)
            if any((isinstance(r, str) or 'time_modified' not in r) for r in raw_rules):
                self.store.save_data(key, manager.export_rules())

        process_rules(self.store.get_data(Crp.KEY_TOP_RULES), self.state.top_rules_manager, Crp.KEY_TOP_RULES)
        process_rules(self.store.get_data(Crp.KEY_RULESET_RULES), self.state.ruleset_rules_manager,
                      Crp.KEY_RULESET_RULES)

    def _upgrade_rule(self, rule_string: str) -> Dict[str, str]:
        rule = ClashRuleParser.parse_rule_line(rule_string)
        remark = 'Manual'
        if isinstance(rule, ClashRule) and rule.rule_type == RoutingRuleType.RULE_SET and rule.payload.startswith(
                self.config.ruleset_prefix):
            remark = 'Auto'
        return {'rule': rule_string, 'remark': remark, 'time_modified': f"{int(time.time())}"}

    def save_proxies(self):
        proxies = self.state.proxies_manager.export_raw(condition=lambda proxy: proxy.remark == 'Manual')
        self.store.save_data(Crp.KEY_PROXIES, proxies)

    def load_proxies(self):
        proxies = self.store.get_data(Crp.KEY_PROXIES) or []
        initial_len = len(proxies)
        proxies.extend(self.state.extra_proxies)
        invalid_proxies = []
        converter = Converter()
        for proxy in proxies:
            try:
                if isinstance(proxy, dict):
                    proxy = UtilsProvider.filter_empty(proxy, empty=['', None])
                    self.state.proxies_manager.add_proxy_dict(proxy, remark='Manual')
                elif isinstance(proxy, str):
                    proxy_dict = converter.convert_line(proxy)
                    if proxy_dict:
                        self.state.proxies_manager.add_proxy_dict(proxy_dict, remark='Manual', raw=proxy)
            except Exception as e:
                logger.error(f"Failed to load proxy {proxy}: {e}")
                invalid_proxies.append(proxy)
        if len(self.state.extra_proxies) != len(invalid_proxies):
            self.state.extra_proxies = invalid_proxies
            self.store.save_data('extra_proxies', self.state.extra_proxies)
        if len(self.state.proxies_manager) > initial_len:
            self.save_proxies()

    def overwrite_proxy(self, proxy: Dict[str, Any]):
        proxy_base = ProxyBase.model_validate(proxy)
        tls = TLSMixin.model_validate(proxy)
        network = NetworkMixin.model_validate(proxy)
        overwrite_config = {
            'base': proxy_base.model_dump(by_alias=True, exclude_none=True),
            'tls': tls.model_dump(by_alias=True, exclude_none=True),
            'network': network.model_dump(by_alias=True, exclude_none=True),
            'lifetime': Crp.OVERWRITTEN_PROXIES_LIFETIME
        }
        self.state.overwritten_proxies[proxy_base.name] = overwrite_config
        self.store.save_data('overwritten_proxies', self.state.overwritten_proxies)

    def remove_overwritten_proxy(self, proxy_name: str):
        self.state.overwritten_proxies.pop(proxy_name, None)
        self.store.save_data('overwritten_proxies', self.state.overwritten_proxies)

    def overwrite_region_group(self, region_group: ProxyGroup):
        overwrite_config = {k: v for k, v in region_group.model_dump(by_alias=True, exclude_none=True).items() if
                            k not in {Crp.KEY_NAME, Crp.KEY_PROXIES, 'use'}}
        self.state.overwritten_region_groups[region_group.root.name] = overwrite_config
        self._group_by_region.cache_clear()
        self.store.save_data('overwritten_region_groups', self.state.overwritten_region_groups)

    def organize_and_save_rules(self):
        self.sync_ruleset()
        self.save_rules()

    def ruleset(self, ruleset: str) -> List[str]:
        if not ruleset.startswith(self.config.ruleset_prefix):
            return []
        action = ruleset[len(self.config.ruleset_prefix):]
        try:
            final_action = Action(action.upper())
        except ValueError:
            final_action = action
        rules = self.state.ruleset_rules_manager.filter_rules_by_action(final_action)
        return [rule.rule.condition_string() for rule in rules]

    def sync_ruleset(self):
        outbounds = set()
        new_outbounds = set()
        manager = self.state.top_rules_manager

        manager.remove_rules_by_lambda(
            lambda r: r.rule.rule_type == RoutingRuleType.RULE_SET and
                      r.remark == 'Auto' and
                      r.rule.payload != f"{self.config.ruleset_prefix}{ClashRuleParser.action_string(r.rule.action)}"
        )
        rules_existed = manager.filter_rules_by_condition(
            lambda r: r.remark == 'Auto' and r.rule.rule_type == RoutingRuleType.RULE_SET
        )
        actions_existed = {ClashRuleParser.action_string(r.rule.action) for r in rules_existed}

        for r in self.state.ruleset_rules_manager.rules:
            action_str = ClashRuleParser.action_string(r.rule.action)
            outbounds.add(action_str)
            if action_str not in actions_existed:
                new_outbounds.add(action_str)

        manager.remove_rules_by_lambda(
            lambda r: r.rule.rule_type == RoutingRuleType.RULE_SET and
                      r.remark == 'Auto' and
                      (ClashRuleParser.action_string(r.rule.action) not in outbounds)
        )

        for outbound in new_outbounds:
            clash_rule = ClashRuleParser.parse_rule_line(
                f"RULE-SET,{self.config.ruleset_prefix}{outbound},{outbound}")
            if clash_rule is None:
                continue
            rule = RuleItem(rule=clash_rule, remark='Auto')
            if not manager.has_rule_item(rule):
                manager.insert_rule_at_priority(rule, 0)

    def append_top_rules(self, rules: List[str]):
        clash_rules = []
        for rule in rules:
            clash_rule = ClashRuleParser.parse_rule_line(rule)
            if clash_rule:
                clash_rules.append(RuleItem(rule=clash_rule, remark='Manual'))
        self.state.top_rules_manager.append_rules(clash_rules)
        self.store.save_data(Crp.KEY_TOP_RULES, self.state.top_rules_manager.export_rules())

    def clash_outbound(self) -> List[Dict[str, Any]]:
        outbound = [{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.value_from_sub_conf('proxy-groups')]
        if self.state.clash_template_dict:
            outbound.extend(
                [{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in
                 self.state.clash_template_dict.get(Crp.KEY_PROXY_GROUPS, [])])
        if self.config.group_by_region or self.config.group_by_country:
            outbound.extend([{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.proxy_groups_by_region()])
        outbound.extend([{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.state.proxy_groups])
        outbound.extend([{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.get_proxies()])
        return outbound

    def rule_providers(self) -> List[Dict[str, Any]]:
        providers_list = []
        sub_providers = self.dict_from_sub_conf('rule-providers')
        hostnames = [UtilsProvider.get_url_domain(url) for url in sub_providers]
        provider_sources = (
            self.state.rule_providers,
            *sub_providers.values(),
            self.state.clash_template_dict.get('rule-providers', {}),
            self.state.acl4ssr_providers
        )
        source_names = ('Manual', *hostnames, 'Template', 'Acl4ssr')
        for i, provider_dict in enumerate(provider_sources):
            for name, value in provider_dict.items():
                provider = copy.deepcopy(value)
                provider[Crp.KEY_NAME] = name
                provider['source'] = source_names[i]
                providers_list.append(provider)
        return providers_list

    def all_proxy_providers(self) -> Dict[str, Any]:
        proxy_providers = self.value_from_sub_conf('proxy-providers')
        proxy_providers.update(self.state.clash_template_dict.get('proxy-providers', {}))
        return proxy_providers

    def get_all_proxies_with_details(self) -> List[Dict[str, Any]]:
        proxies = self.get_proxies(regex='^Manual$', flat=False)
        proxies.extend(self.get_proxies(regex='^Template$', flat=False))
        proxies.extend(self.get_proxies(regex='^Sub:', flat=False))
        ret = []
        for proxy in proxies:
            remark = proxy['remark']
            i = remark.rfind('-')
            source = remark[remark.rfind(':') + 1:(len(remark) if i == -1 else i)]
            if isinstance(proxy['raw'], str):
                proxy_link = proxy['raw']
            else:
                try:
                    proxy_link = Converter.convert_to_share_link(proxy['proxy'])
                except Exception as e:
                    logger.warn(f"Failed to convert proxy link: {repr(e)}")
                    proxy_link = None
            proxy['proxy']['source'] = source
            proxy['proxy']['v2ray_link'] = proxy_link
            proxy['proxy']['overwritten'] = proxy['proxy'][Crp.KEY_NAME] in self.state.overwritten_proxies
            ret.append(proxy['proxy'])
        ret.extend([{'source': 'Invalid', 'v2ray_link': None, **proxy} for proxy in self.state.extra_proxies])
        return ret

    def delete_proxy(self, name: str):
        extra_proxies = [p for p in self.state.extra_proxies if p.get(Crp.KEY_NAME) != name]
        if len(extra_proxies) != len(self.state.extra_proxies):
            self.state.extra_proxies = extra_proxies
            self.store.save_data('extra_proxies', self.state.extra_proxies)
            return
        self.state.proxies_manager.remove_proxy(name)
        self.save_proxies()

    def import_proxies(self, params: Dict[str, Any]) -> Tuple[bool, str]:
        extra_proxies = ClashRuleProviderService.parse_proxies_from_input(params)
        if not extra_proxies:
            return False, "无可用节点或输入格式错误"
        success_count = 0
        error_messages = ''
        success = True
        for proxy_item in extra_proxies:
            try:
                self.state.proxies_manager.add_proxy_dict(
                    proxy_item['proxy'], 'Manual', raw=proxy_item['raw']
                )
                success_count += 1
            except Exception as err:
                success = False
                error_messages += f"{err}\n"
        message = f"导入 {success_count}/{len(extra_proxies)} 个代理节点. \n{error_messages}"
        self.save_proxies()
        return success, message

    def update_proxy(self, name: str, params: Dict[str, Any]) -> Tuple[bool, str]:
        proxy_dict = params
        previous_name = name
        if previous_name not in self.state.proxies_manager:
            return False, f"The proxy name {previous_name} does not exist"
        if proxy_dict.get('rescind'):
            self.remove_overwritten_proxy(previous_name)
            return True, ''
        try:
            Proxy.model_validate(proxy_dict)
            if proxy_dict[Crp.KEY_NAME] != previous_name:
                return False, "Proxy name is not allowed to be overwritten"
            self.overwrite_proxy(proxy_dict)
        except Exception as e:
            logger.error(f"Failed to overwrite proxy: {repr(e)}")
            return False, "覆写代理失败"
        return True, ''

    @staticmethod
    def parse_proxies_from_input(params: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:
        extra_proxies: List = []
        if params.get('type') == 'YAML':
            try:
                imported_proxies = yaml.load(params["payload"], Loader=yaml.SafeLoader)
                if not imported_proxies or not isinstance(imported_proxies, dict):
                    logger.error(f"Failed to load YAML payload: {repr(params)}")
                    return None
                if Crp.KEY_PROXIES not in imported_proxies:
                    logger.error("No field 'proxies' found")
                    return None
                extra_proxies = [{'proxy': proxy, 'raw': None} for proxy in imported_proxies.get(Crp.KEY_PROXIES, [])]
            except Exception as err:
                logger.error(f"Failed to load YAML payload: {params['payload']}: {repr(err)}")
                return None
        elif params.get('type') == 'LINK':
            try:
                links = params['payload'].strip().splitlines()
                for link in links:
                    proxy = Converter().convert_line(link, skip_exception=True)
                    if proxy:
                        extra_proxies.append({'proxy': proxy, 'raw': link})
            except Exception as err:
                logger.error(f"Failed to load LINK payload: {repr(params)}: {repr(err)}")
        return extra_proxies

    def get_all_proxy_groups_with_source(self) -> List[Dict[str, Any]]:
        proxy_groups = []
        sub = self.dict_from_sub_conf('proxy-groups')
        hostnames = [UtilsProvider.get_url_domain(url) or '' for url in sub]
        sub_proxy_groups = sub.values()
        sources = ('Manual', 'Template', *hostnames, 'Region')
        groups = (self.state.proxy_groups, self.state.clash_template_dict.get(Crp.KEY_PROXY_GROUPS, []),
                  *sub_proxy_groups, self.proxy_groups_by_region())
        for i, group in enumerate(groups):
            for proxy_group in group:
                proxy_group_copy = copy.deepcopy(proxy_group)
                proxy_group_copy['source'] = sources[i]
                proxy_groups.append(proxy_group_copy)
        return proxy_groups

    def get_proxies(self, regex: Optional[str] = None, flat: bool = True) -> List[Dict[str, Any]]:
        def _overwrite_proxy(_proxy: Dict[str, Any]) -> Dict[str, Any]:
            if _proxy[Crp.KEY_NAME] in self.state.overwritten_proxies:
                for key in ['base', 'tls', 'network']:
                    _proxy.update(copy.deepcopy(self.state.overwritten_proxies[_proxy[Crp.KEY_NAME]].get(key)) or {})
            return _proxy

        if regex is None:
            proxy_items = list(self.state.proxies_manager)
        else:
            compiled = re.compile(regex)
            proxy_items = self.state.proxies_manager.filter_proxies_by_condition(
                lambda item: bool(compiled.match(item.remark))
            )

        result = []
        for p in proxy_items:
            if any(keyword in p.proxy.name for keyword in self.config.filter_keywords):
                continue
            proxy_dict = _overwrite_proxy(p.proxy.model_dump(by_alias=True, exclude_none=True))
            if flat:
                result.append(proxy_dict)
            else:
                result.append({'proxy': proxy_dict, 'raw': p.raw, 'remark': p.remark})
        return result

    @cached(maxsize=1, ttl=86400, skip_empty=True)
    def _get_countries_data(self) -> List[Dict[str, str]]:
        file_path = settings.ROOT_PATH / 'app' / 'plugins' / self.plugin_id.lower() / 'countries.json'
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            logger.error(f"加载国家/地区文件错误：{e}")
            return []

    def proxy_groups_by_region(self) -> List[Dict[str, Any]]:
        countries = self._get_countries_data()
        all_proxies = self.get_proxies()
        return self._group_by_region(countries, all_proxies, self.config.group_by_region,
                                     self.config.group_by_country,
                                     self.state.overwritten_region_groups)

    @cached(maxsize=1, ttl=86400)
    def _group_by_region(self, countries: List[Dict[str, str]], all_proxies: List[Dict[str, Any]],
                         group_by_region: bool, group_by_country: bool,
                         overwritten_groups: Dict[str, Any]) -> List[Dict[str, Any]]:
        continent_groups = {}
        country_groups = {}
        continent_map = {
            '欧洲': 'Europe', '亚洲': 'Asia', '大洋洲': 'Oceania', '非洲': 'Africa',
            '北美洲': 'NorthAmerica', '南美洲': 'SouthAmerica'
        }
        proxy_groups: List[Dict[str, Any]] = []
        hk = next((c for c in countries if c['abbr'] == 'HK'), {})
        tw = next((c for c in countries if c['abbr'] == 'TW'), {})

        for proxy_node in all_proxies:
            country = ClashRuleProviderService._country_from_node(countries, proxy_node[Crp.KEY_NAME])
            if not country:
                continue
            if country.get("abbr") == "CN":
                if any(key in proxy_node[Crp.KEY_NAME] for key in ("🇭🇰", "HK", "香港")):
                    country = hk
                if any(key in proxy_node[Crp.KEY_NAME] for key in ("🇹🇼", "TW", "台湾")):
                    country = tw
            continent = continent_map.get(country['continent'])
            if continent and group_by_region:
                continent_groups.setdefault(continent, []).append(proxy_node[Crp.KEY_NAME])
            if group_by_country:
                country_groups.setdefault(f"{country.get('emoji')} {country.get('chinese')}", []).append(
                    proxy_node[Crp.KEY_NAME])
        for continent, nodes in continent_groups.items():
            if nodes:
                proxy_groups.append({Crp.KEY_NAME: continent, 'type': 'select', Crp.KEY_PROXIES: nodes})

        excluded = ('中国', '香港', 'CN', 'HK', '🇨🇳', '🇭🇰')
        for continent_node in continent_groups.get('Asia', []):
            if any(x in continent_node for x in excluded):
                continue
            continent_groups.setdefault('AsiaExceptChina', []).append(continent_node)
        if continent_groups.get('AsiaExceptChina'):
            proxy_group = {Crp.KEY_NAME: 'AsiaExceptChina', 'type': 'select',
                           Crp.KEY_PROXIES: continent_groups['AsiaExceptChina']}
            proxy_groups.append(proxy_group)
        for country, nodes in country_groups.items():
            if len(nodes):
                proxy_group = {Crp.KEY_NAME: country, 'type': 'select', Crp.KEY_PROXIES: nodes}
                proxy_groups.append(proxy_group)
        country_group = list(country_groups.keys())
        if country_group:
            proxy_groups.append({Crp.KEY_NAME: '🏴‍☠️国家分组', 'type': 'select', Crp.KEY_PROXIES: country_group})

        for pg in proxy_groups:
            if pg[Crp.KEY_NAME] in overwritten_groups:
                pg.update(overwritten_groups[pg[Crp.KEY_NAME]])

        return proxy_groups

    @staticmethod
    def _country_from_node(countries: List[Dict[str, str]], node_name: str) -> Optional[Dict[str, str]]:
        node_name_lower = node_name.lower()
        for country in countries:
            if country.get('emoji') and country['emoji'] in node_name:
                return country
            if (
                    (country.get('chinese') and country['chinese'] in node_name) or
                    (country.get('english') and country['english'].lower() in node_name_lower)
            ):
                return country
        return None

    @staticmethod
    def _extend_with_name_checking(to_list: List[Dict[str, Any]], from_list: List[Dict[str, Any]]
                                   ) -> List[Dict[str, Any]]:
        """
        去除同名元素合并列表
        """
        for item in from_list:
            if any(p.get(Crp.KEY_NAME) == item.get(Crp.KEY_NAME, '') for p in to_list):
                logger.warn(f"Item named {item.get(Crp.KEY_NAME)!r} already exists. Skipping...")
                continue
            to_list.append(item)
        return to_list

    @staticmethod
    def _remove_invalid_outbounds(proxies: List[Dict[str, Any]], proxy_groups: List[Dict[str, Any]]
                                  ) -> List[Dict[str, Any]]:
        """
        从代理组中移除无效的出站
        """
        outbounds = {proxy.get(Crp.KEY_NAME) for proxy in proxies if proxy.get(Crp.KEY_NAME)} | \
                    {proxy_group.get(Crp.KEY_NAME) for proxy_group in proxy_groups if proxy_group.get(Crp.KEY_NAME)} | \
                    {action.value for action in Action}
        outbounds.add('GLOBAL')
        for proxy_group in proxy_groups:
            ps = []
            if proxy_group.get(Crp.KEY_PROXIES):
                for proxy in proxy_group.get(Crp.KEY_PROXIES, []):
                    if proxy in outbounds:
                        ps.append(proxy)
                    else:
                        logger.warn(f"Proxy {proxy!r} in {proxy_group.get(Crp.KEY_NAME)!r} doesn't exist. Skipping...")
                proxy_group[Crp.KEY_PROXIES] = ps
        return proxy_groups

    @staticmethod
    def _remove_invalid_proxy_providers(providers: Dict[str, Any], proxy_groups: List[Dict[str, Any]]
                                        ) -> List[Dict[str, Any]]:
        provider_names = providers.keys()
        for proxy_group in proxy_groups:
            ps = []
            if proxy_group.get('use'):
                for provider in proxy_group.get('use', []):
                    if provider in provider_names:
                        ps.append(provider)
                    else:
                        logger.warn(f"Proxy provider {provider!r} in {proxy_group.get(Crp.KEY_NAME)!r} doesn't exist. "
                                    f"Skipping...")
                proxy_group['use'] = ps
        return proxy_groups

    @staticmethod
    def _build_graph(config: Dict[str, Any]) -> Dict[str, Any]:
        """构建代理组有向图"""
        graph = {}
        groups = config.get(Crp.KEY_PROXY_GROUPS, [])
        group_names = {g[Crp.KEY_NAME] for g in groups}
        for group in groups:
            name = group[Crp.KEY_NAME]
            proxies = group.get(Crp.KEY_PROXIES, [])
            graph[name] = [p for p in proxies if p in group_names]
        return graph

    @overload
    def value_from_sub_conf(self, key: Literal['rules', 'proxies', 'proxy-groups']) -> list: ...

    @overload
    def value_from_sub_conf(self, key: Literal['rule-providers', 'proxy-providers']) -> dict: ...

    def value_from_sub_conf(self,
                            key: Literal['rules', 'rule-providers', 'proxies', 'proxy-groups', 'proxy-providers']):
        default = copy.deepcopy(Crp.DEFAULT_CLASH_CONF[key])
        for conf in self.config.subscriptions_config:
            url = conf.url
            sub_config = self.state.clash_configs.get(url, {})
            if isinstance(default, dict):
                default.update(sub_config.get(key, {}))
            elif isinstance(default, list):
                default.extend(sub_config.get(key, []))
        return default

    def dict_from_sub_conf(self,
                           key: Literal['rules', 'rule-providers', 'proxies', 'proxy-groups', 'proxy-providers']
                           ) -> Dict[str, Any]:
        result = {}
        for conf in self.config.subscriptions_config:
            url = conf.url
            sub_config = self.state.clash_configs.get(url, {})
            result[url] = sub_config.get(key, copy.deepcopy(Crp.DEFAULT_CLASH_CONF[key]))
        return result

    async def fetch_clash_data(self, endpoint: str) -> Dict:
        headers = {"Authorization": f"Bearer {self.config.dashboard_secret}"}
        url = f"{self.config.dashboard_url}/{endpoint}"
        response = await AsyncRequestUtils().get_json(url, headers=headers, timeout=10)
        if response is None:
            raise HTTPException(status_code=502, detail=f"Failed to fetch {endpoint}")
        return response

    def get_subscription_user_info(self) -> Dict[str, int]:
        sub_info = {'upload': 0, 'download': 0, 'total': 0, 'expire': 0}
        for info in self.state.subscription_info.values():
            if not info:
                continue
            sub_info['upload'] += info.get('upload', 0)
            sub_info['download'] += info.get('download', 0)
            sub_info['total'] += info.get('total', 0)
            sub_info['expire'] = max(sub_info['expire'], info.get('expire') or 0)
        return sub_info

    @staticmethod
    async def async_notify_clash(ruleset: str, api_url: str, api_secret: str):
        """
        通知 Clash 刷新规则集
        """
        logger.info(f"正在刷新 [{ruleset}] {api_url} ...")
        url = f'{api_url}/providers/rules/{ruleset}'
        resp = await AsyncRequestUtils(content_type="application/json",
                                       headers={"authorization": f"Bearer {api_secret}"}
                                       ).put_res(url)
        if resp and resp.status_code == 204:
            logger.info(f"[{ruleset}] {api_url} 刷新完成")
        else:
            logger.warn(f"[{ruleset}] {api_url} 刷新失败")

    def add_notification_job(self, ruleset_names: List[str]):
        if not self.config.enabled or not self.scheduler:
            return
        for ruleset in ruleset_names:
            if ruleset in self.state.rule_provider:
                self.scheduler.add_job(
                    ClashRuleProviderService.async_notify_clash, "date",
                    run_date=datetime.now(tz=pytz.timezone(settings.TZ)) +
                             timedelta(seconds=self.config.refresh_delay),
                    args=(ruleset, self.config.dashboard_url,
                          self.config.dashboard_secret),
                    id=f'CRP-notify-clash{ruleset}', replace_existing=True,
                    misfire_grace_time=Crp.MISFIRE_GRACE_TIME
                )

    def clash_config(self) -> Optional[Dict[str, Any]]:
        if not self.state.clash_template_dict:
            config: Dict[str, Any] = copy.deepcopy(Crp.DEFAULT_CLASH_CONF)
        else:
            config: Dict[str, Any] = copy.deepcopy(self.state.clash_template_dict)

        for key, default in Crp.DEFAULT_CLASH_CONF.items():
            if isinstance(default, dict):
                assert key in {'rule-providers', 'proxy-providers'}
                UtilsProvider.update_with_checking(self.value_from_sub_conf(key), config.get(key, {}))
            elif isinstance(default, list):
                assert key in {'rules', 'proxies', 'proxy-groups'}
                self._extend_with_name_checking(config.get(key, []), self.value_from_sub_conf(key))
        proxies = []
        for proxy in self.get_proxies():
            if any(p.get(Crp.KEY_NAME) == proxy.get(Crp.KEY_NAME, '') for p in proxies):
                logger.warn(f"Proxy named {proxy.get(Crp.KEY_NAME)!r} already exists. Skipping...")
                continue
            proxies.append(proxy)
        if proxies:
            config[Crp.KEY_PROXIES] = proxies
        self.sync_ruleset()
        # 添加代理组
        proxy_groups = copy.deepcopy(self.state.proxy_groups)
        if proxy_groups:
            config[Crp.KEY_PROXY_GROUPS] = self._extend_with_name_checking(config[Crp.KEY_PROXY_GROUPS],
                                                                           proxy_groups)
        # 添加按大洲代理组
        if self.config.group_by_region or self.config.group_by_country:
            groups_by_region = self.proxy_groups_by_region()
            if groups_by_region:
                config[Crp.KEY_PROXY_GROUPS] = self._extend_with_name_checking(config[Crp.KEY_PROXY_GROUPS],
                                                                               groups_by_region)
        # 移除无效出站, 避免配置错误
        config[Crp.KEY_PROXY_GROUPS] = self._remove_invalid_outbounds(config.get(Crp.KEY_PROXIES, []),
                                                                      config.get(Crp.KEY_PROXY_GROUPS, []))
        config[Crp.KEY_PROXY_GROUPS] = self._remove_invalid_proxy_providers(
            self.all_proxy_providers(),
            config.get(Crp.KEY_PROXY_GROUPS, [])
        )
        top_rules = []
        outbound_names = list(x.get(Crp.KEY_NAME) for x in self.clash_outbound())

        # 添加 extra rule providers
        if self.state.rule_providers:
            config['rule-providers'].update(self.state.rule_providers)

        # 通过 ruleset rules 添加 rule-providers
        self.state.rule_provider = {}
        for r in self.state.ruleset_rules_manager.rules:
            rule = r.rule
            action_str = ClashRuleParser.action_string(rule.action)
            rule_provider_name = f'{self.config.ruleset_prefix}{action_str}'
            if rule_provider_name not in self.state.rule_provider:
                path_name = hashlib.sha256(action_str.encode('utf-8')).hexdigest()[:10]
                self.state.ruleset_names[path_name] = rule_provider_name
                sub_url = (f"{self.config.movie_pilot_url}/api/v1/plugin/ClashRuleProvider/ruleset?"
                           f"name={path_name}&apikey={self.config.apikey or settings.API_TOKEN}")
                self.state.rule_provider[rule_provider_name] = {"behavior": "classical",
                                                                "format": "yaml",
                                                                "interval": 3600,
                                                                "path": f"./CRP/{path_name}.yaml",
                                                                "type": "http",
                                                                "url": sub_url}
        config['rule-providers'].update(self.state.rule_provider)
        # 添加规则
        for r in self.state.top_rules_manager:
            rule = r.rule
            if (not isinstance(rule.action, Action) and rule.action not in outbound_names and
                    rule.rule_type != RoutingRuleType.SUB_RULE):
                logger.warn(f"出站 {rule.action} 不存在, 跳过 {rule.raw_rule}")
                continue
            if rule.rule_type == RoutingRuleType.RULE_SET:
                # 添加ACL4SSR Rules
                if rule.payload in self.state.acl4ssr_providers:
                    config['rule-providers'][rule.payload] = self.state.acl4ssr_providers[rule.payload]
                if rule.payload not in config.get('rule-providers', {}):
                    logger.warn(f"规则集合 {rule.payload!r} 不存在, 跳过 {rule.raw_rule!r}")
                    continue
            top_rules.append(str(rule))
        for raw_rule in config.get("rules", []):
            rule = ClashRuleParser.parse_rule_line(raw_rule)
            if not rule:
                logger.warn(f"无效的规则 {raw_rule!r}, 跳过")
                continue

            if (not isinstance(rule.action, Action) and rule.action not in outbound_names and
                    rule.rule_type != RoutingRuleType.SUB_RULE):
                logger.warn(f"出站 {rule.action!r} 不存在, 跳过 {rule.raw_rule!r}")
                continue
            top_rules.append(str(rule))
        config["rules"] = top_rules

        # 添加 Hosts
        if self.state.hosts:
            config.setdefault('hosts', {})
            new_hosts = {
                item['domain']: item.get('value', []) if not item.get(
                    'using_cloudflare') else self.config.best_cf_ip
                for item in self.state.hosts if item.get('domain')
            }
            config["hosts"] = {**config["hosts"], **new_hosts}

        if self.state.rule_provider:
            config['rule-providers'] = config.get('rule-providers') or {}
            config['rule-providers'].update(self.state.rule_provider)

        key_to_delete = []
        for key, item in self.state.ruleset_names.items():
            if item not in config.get('rule-providers', {}):
                key_to_delete.append(key)
        for key in key_to_delete:
            del self.state.ruleset_names[key]
        if not config.get("rule-providers"):
            del config["rule-providers"]

        # 对代理组进行回环检测
        proxy_graph = self._build_graph(config)
        cycles = UtilsProvider.find_cycles(proxy_graph)
        # 警告但不处理
        if cycles:
            logger.warn("发现代理组回环：")
            for cycle in cycles:
                logger.warn(" -> ".join(cycle))

        self.store.save_data('ruleset_names', self.state.ruleset_names)
        self.store.save_data('rule_provider', self.state.rule_provider)
        return config

    def delete_proxy_group(self, name: str) -> Tuple[bool, str]:
        """
        Deletes a proxy group by name and saves the state.
        Returns True if a group was deleted, False otherwise.
        """
        initial_len = len(self.state.proxy_groups)
        self.state.proxy_groups = [item for item in self.state.proxy_groups if item.get(Crp.KEY_NAME) != name]
        if len(self.state.proxy_groups) < initial_len:
            self.store.save_data('proxy_groups', self.state.proxy_groups)
            return True, ''
        return False, ''

    def add_proxy_group(self, item: ProxyGroup) -> Tuple[bool, str]:
        """
        Adds a new proxy group, saves the state, and returns status.
        """
        if any(x.get(Crp.KEY_NAME) == item.root.name for x in self.state.proxy_groups):
            return False, f"The proxy group name {item.root.name} already exists"
        try:
            proxy_group = ProxyGroup.model_validate(item)
        except Exception as e:
            logger.error(f"Failed to parse proxy group: {repr(e)}")
            return False, "Failed to parse proxy group"
        self.state.proxy_groups.append(proxy_group.model_dump(by_alias=True, exclude_none=True))
        self.store.save_data('proxy_groups', self.state.proxy_groups)
        return True, "Proxy group added successfully."

    def update_proxy_group(self, previous_name: str, item: ProxyGroup) -> Tuple[bool, str]:
        proxy_group = item.root
        region_groups = {g[Crp.KEY_NAME] for g in self.proxy_groups_by_region()}
        if previous_name in region_groups:
            self.overwrite_region_group(item)
            return True, ''

        index = next((i for i, x in enumerate(self.state.proxy_groups) if x.get(Crp.KEY_NAME) == previous_name), None)
        if index is None:
            return False, f"Proxy group {previous_name!r} does not exist"

        new_name_index = next((i for i, x in enumerate(self.state.proxy_groups) if
                               x.get(Crp.KEY_NAME) == proxy_group.name), None)
        if new_name_index is not None and new_name_index != index:
            return False, f"The proxy group name {proxy_group.name} already exists"
        self.state.proxy_groups[index] = proxy_group.model_dump(by_alias=True, exclude_none=True)
        self.store.save_data('proxy_groups', self.state.proxy_groups)
        return True, ''

    def update_rule_provider(self, name: str, rule_provider_data: RuleProviderData) -> Tuple[bool, str]:
        """
        Updates a rule provider, saves the state, and returns status.
        """
        new_name = rule_provider_data.name
        if name != new_name:
            self.state.rule_providers.pop(name, None)
        self.state.rule_providers[new_name] = rule_provider_data.rule_provider.model_dump(by_alias=True,
                                                                                    exclude_none=True)
        self.store.save_data('extra_rule_providers', self.state.rule_providers)
        return True, "Rule provider updated successfully."

    def delete_rule_provider(self, name: str):
        self.state.rule_providers.pop(name, None)
        self.store.save_data('extra_rule_providers', self.state.rule_providers)

    async def test_connectivity(self, clash_apis: List[ClashApi], sub_links: List[str]) -> Tuple[bool, str]:
        tasks = []
        urls = []
        for d in clash_apis:
            headers = {"authorization": f"Bearer {d.secret}"}
            url = f"{d.url}/version"
            task = asyncio.create_task(
                AsyncRequestUtils(accept_type="application/json", headers=headers, timeout=5).get_res(url)
            )
            urls.append(url)
            tasks.append(task)
        for sub_link in sub_links:
            task = asyncio.create_task(
                AsyncRequestUtils(
                    accept_type="text/html", proxies=settings.PROXY if self.config.proxy else None,
                    timeout=5).get(sub_link)
            )
            urls.append(sub_link)
            tasks.append(task)
        results = await asyncio.gather(*tasks)
        for i, result in enumerate(results):
            if not result:
                return False, f"无法连接到 {urls[i]}"
        return True, ""

    def get_status(self) -> Dict[str, Any]:
        data = {
            "state": self.config.enabled,
            "ruleset_prefix": self.config.ruleset_prefix,
            "best_cf_ip": self.config.best_cf_ip,
            "geoRules": self.state.geo_rules,
            "subscription_info": self.state.subscription_info,
            "sub_url": f"{self.config.movie_pilot_url}/api/v1/plugin/{self.plugin_id}/config?"
                       f"apikey={self.config.apikey or settings.API_TOKEN}"
        }
        return data

    def get_rules(self, rule_type: str) -> List[Dict[str, Any]]:
        manager = self.state.ruleset_rules_manager \
            if rule_type == 'ruleset' else self.state.top_rules_manager
        return manager.to_list()

    def reorder_rules(self, rule_type: str, moved_priority: int, target_priority: int) -> Tuple[bool, str]:
        try:
            if rule_type == 'ruleset':
                rule = self.state.ruleset_rules_manager.reorder_rules(moved_priority, target_priority)
                self.add_notification_job(
                    [f"{self.config.ruleset_prefix}{rule.rule.action}"])
            else:
                self.state.top_rules_manager.reorder_rules(moved_priority, target_priority)
        except Exception as e:
            logger.info(f"Failed to reorder rules: {repr(e)}")
            return False, "规则移动失败"
        self.organize_and_save_rules()
        return True, ""

    def update_rule(self, rule_type: str, priority: int, rule_data: RuleData) -> Tuple[bool, str]:
        try:
            dst_priority = rule_data.priority
            src_priority = priority
            clash_rule = ClashRuleParser.parse_rule_dict(rule_data.model_dump(exclude_none=True))
            if not clash_rule:
                return False, f"无效的规则: {rule_data!r}"
            if rule_type == 'ruleset':
                manager = self.state.ruleset_rules_manager
                original_rule = manager.get_rule_at_priority(src_priority)
                rule_item = RuleItem(rule=clash_rule, remark=original_rule.remark, time_modified=time.time())
                res = manager.update_rule_at_priority(rule_item, src_priority, dst_priority)
                if res:
                    ruleset_to_notify = [f"{self.config.ruleset_prefix}{clash_rule.action}"]
                    if rule_data.action != original_rule.rule.action:
                        ruleset_to_notify.append(f"{self.config.ruleset_prefix}{original_rule.rule.action}")
                    self.add_notification_job(ruleset_to_notify)
            else:
                manager = self.state.top_rules_manager
                original_rule = manager.get_rule_at_priority(src_priority)
                rule_item = RuleItem(rule=clash_rule, remark=original_rule.remark, time_modified=time.time())
                res = manager.update_rule_at_priority(rule_item, src_priority, dst_priority)
        except Exception as err:
            logger.info(f"Failed to update rules: {repr(err)}")
            return False, "更新规则出错"
        self.organize_and_save_rules()
        return res, ""

    def add_rule(self, rule_type: str, rule_data: RuleData) -> Tuple[bool, str]:
        try:
            priority = rule_data.priority
            clash_rule = ClashRuleParser.parse_rule_dict(rule_data.model_dump(exclude_none=True))
            if not clash_rule:
                return False, f"无效的输入规则: {rule_data.model_dump(exclude_none=True)}"
            rule_item = RuleItem(rule=clash_rule, remark='Manual', time_modified=time.time())
            if rule_type == 'ruleset':
                self.state.ruleset_rules_manager.insert_rule_at_priority(rule_item, priority)
                self.add_notification_job([f"{self.config.ruleset_prefix}{clash_rule.action}"])
            else:
                self.state.top_rules_manager.insert_rule_at_priority(rule_item, priority)
        except Exception as err:
            logger.info(f"Failed to add rule: {repr(err)}")
            return False, "添加规则出错"
        self.organize_and_save_rules()
        return True, ""

    def delete_rule(self, rule_type: str, priority: int):
        if rule_type == 'ruleset':
            res = self.state.ruleset_rules_manager.remove_rule_at_priority(priority)
            if res:
                rule = res.rule
                action_str = ClashRuleParser.action_string(rule.action)
                self.add_notification_job([f"{self.config.ruleset_prefix}{action_str}"])
        else:
            self.state.top_rules_manager.remove_rule_at_priority(priority)
        self.organize_and_save_rules()

    def import_rules(self, params: Dict[str, Any]) -> Tuple[bool, str]:
        rules: List[str] = []
        if params.get('type') == 'YAML':
            try:
                imported_rules = yaml.load(params["payload"], Loader=yaml.SafeLoader)
                if not isinstance(imported_rules, dict):
                    return False, "无效的输入"
                rules = imported_rules.get("rules", [])
            except yaml.YAMLError as err:
                logger.error(f"Failed to import rules: {repr(err)}")
                return False, 'YAML 格式错误'
        self.append_top_rules(rules)
        return True, ""

    def get_ruleset(self, name: str) -> Optional[str]:
        ruleset_name = self.state.ruleset_names.get(name)
        if ruleset_name is None:
            return None
        rules = self.ruleset(ruleset_name)
        res = yaml.dump({"payload": rules}, allow_unicode=True)
        return res

    def get_hosts(self) -> List[Dict[str, Any]]:
        return self.state.hosts

    def update_hosts(self, param: HostData) -> Tuple[bool, str]:
        if not param.value:
            return False, "无效的参数"
        value = param.value.model_dump(exclude_none=True)
        for i, host in enumerate(self.state.hosts):
            if host.get('domain') == param.domain:
                self.state.hosts[i] = {**host, **value}
                self.store.save_data('hosts', self.state.hosts)
                logger.info(f"Host for domain {param.domain} updated successfully.")
                return True, ""
        self.state.hosts.append(value)
        self.store.save_data('hosts', self.state.hosts)
        return True, ""

    def delete_host(self, param: HostData) -> Tuple[bool, str]:
        original_len = len(self.state.hosts)
        self.state.hosts = [host for host in self.state.hosts if host.get('domain') != param.domain]
        self.store.save_data('hosts', self.state.hosts)

        if len(self.state.hosts) < original_len:
            return True, ''
        else:
            return False, f'Host for domain {param.domain} not found.'

    async def refresh_subscription(self, url: str) -> Tuple[bool, str]:
        sub_conf = next((conf for conf in self.config.subscriptions_config if conf.url == url), None)
        if not sub_conf:
            return False, f"Configuration for {url} not found."
        config, info = await self.async_get_subscription(url, sub_conf.model_dump())
        if not config:
            return False, f"订阅链接 {url} 更新失败"

        self.state.clash_configs[url] = config
        self.__sync_sub_proxies(url, config)
        self.state.subscription_info[url] = {**info,
            'enabled': self.state.subscription_info.get(url, {}).get(
                'enabled', True)}
        self.store.save_data('clash_configs', self.state.clash_configs)
        self.store.save_data('subscription_info', self.state.subscription_info)
        return True, "订阅更新成功"

    def update_subscription_info(self, sub_info: SubscriptionInfo):
        self.state.subscription_info[sub_info.url][sub_info.field] = sub_info.value
        self.store.save_data('subscription_info', self.state.subscription_info)

    def add_proxies_to_manager(self, proxies: List[Dict[str, Any]], remark: str, raw: Optional[str] = None):
        for proxy in proxies:
            try:
                self.state.proxies_manager.add_proxy_dict(proxy, remark=remark, raw=raw)
            except Exception as e:
                logger.error(f"Failed to add proxies: {e}")

    async def async_get_subscription(self, url: str, conf: Dict[str, Any]
                                     ) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
        if not url:
            return None, None
        logger.info(f"正在刷新 {UtilsProvider.get_url_domain(url)} ...")
        ret = None
        for _ in range(self.config.retry_times):
            ret = await AsyncRequestUtils(accept_type="text/html", timeout=self.config.timeout,
                                          proxies=settings.PROXY if self.config.proxy else None
                                          ).get_res(url)
            if ret:
                break
        if not ret:
            logger.warning(f"{UtilsProvider.get_url_domain(url)} 刷新失败.")
            return None, None
        try:
            content = ret.content
            rs = yaml.safe_load(content)
            if isinstance(rs, str):
                proxies = Converter().convert_v2ray(content)
                if not proxies:
                    raise ValueError("Unknown content type")
                rs = {Crp.KEY_PROXIES: proxies,
                      Crp.KEY_PROXY_GROUPS: [
                          {Crp.KEY_NAME: "All Proxies", 'type': 'select', 'include-all-proxies': True}]}

            if not isinstance(rs, dict):
                raise ValueError("Subscription content is not a valid dictionary.")
            rs: dict[str, Any] = rs
            logger.info(f"已刷新: {UtilsProvider.get_url_domain(url)}. 节点数量: {len(rs.get(Crp.KEY_PROXIES, []))}")
            for key, default in Crp.DEFAULT_CLASH_CONF.items():
                rs.setdefault(key, copy.deepcopy(default))
                if not conf.get(key, False):
                    rs[key] = copy.deepcopy(default)
        except Exception as e:
            logger.error(f"解析配置出错： {e}")
            return None, None

        sub_info = {'last_update': int(time.time()), 'proxy_num': len(rs.get(Crp.KEY_PROXIES, []))}
        if 'Subscription-Userinfo' in ret.headers:
            matches = re.findall(r'(\w+)=(\d+)', ret.headers['Subscription-Userinfo'])
            variables = {key: int(value) for key, value in matches}
            sub_info.update(variables)
        return rs, sub_info

    async def async_refresh_subscriptions(self) -> Dict[str, bool]:
        res = {}
        for sub_conf in self.config.subscriptions_config:
            url = sub_conf.url
            if not self.state.subscription_info.get(url, {}).get('enabled'):
                continue
            conf, sub_info = await self.async_get_subscription(url, conf=sub_conf.model_dump())
            if not conf:
                res[url] = False
                continue
            self.state.subscription_info[url] = {**sub_info, 'enabled': True}
            res[url] = True
            self.state.clash_configs[url] = conf
            self.__sync_sub_proxies(url, conf)
        self.store.save_data('subscription_info', self.state.subscription_info)
        self.store.save_data('clash_configs', self.state.clash_configs)
        return res

    def __sync_sub_proxies(self, url: str, conf: Dict[str, Any]):
        remark = f"Sub:{UtilsProvider.get_url_domain(url)}-{abs(hash(url))}"
        self.state.proxies_manager.remove_proxies_by_condition(lambda p: p.remark == remark)
        self.add_proxies_to_manager(conf.get(Crp.KEY_PROXIES, []), remark)

    async def async_refresh_acl4ssr(self):
        logger.info("正在刷新 ACL4SSR ...")
        paths = ['Clash/Providers', 'Clash/Providers/Ruleset']
        api_url = f"{Crp.ACL4SSR_API}/contents/%s"
        branch = 'master'
        new_providers = {}
        for path in paths:
            response = await AsyncRequestUtils().get_res(api_url % path, headers=settings.GITHUB_HEADERS,
                                                         params={'ref': branch})
            if not response:
                continue
            files = response.json()
            yaml_files = [f for f in files if f["type"] == "file" and f[Crp.KEY_NAME].endswith((".yaml", ".yml"))]
            for f in yaml_files:
                name = f"{self.config.acl4ssr_prefix}{f[Crp.KEY_NAME][:f[Crp.KEY_NAME].rfind('.')]}"
                file_path = f"./ACL4SSR/{f['name']}"
                provider = {'type': 'http', 'path': file_path, 'url': f["download_url"], 'interval': 600,
                            'behavior': 'classical', 'format': 'yaml', 'size-limit': 0}
                if name not in new_providers:
                    new_providers[name] = provider
        self.state.acl4ssr_providers = new_providers
        self.store.save_data('acl4ssr_providers', self.state.acl4ssr_providers)
        logger.info(f"ACL4SSR 规则集刷新完成. 规则集数量: {len(self.state.acl4ssr_providers)}")

    async def async_refresh_geo_dat(self):
        logger.info("正在刷新 Geo Rules ...")
        branch = 'meta'
        api_url = f"{Crp.METACUBEX_RULE_DAT_API}/contents/geo"
        resp = await AsyncRequestUtils().get_res(api_url, headers=settings.GITHUB_HEADERS, params={'ref': branch})
        if not resp:
            return

        for path in resp.json():
            if path["type"] == "dir" and path["name"] in self.state.geo_rules:
                tree_sha = path["sha"]
                url = f"{Crp.METACUBEX_RULE_DAT_API}/git/trees/{tree_sha}"
                res = await AsyncRequestUtils().get_res(url, headers=settings.GITHUB_HEADERS, params={'ref': branch})
                if not res:
                    continue
                tree = res.json()
                yaml_files = [item["path"][:item["path"].rfind('.')] for item in tree["tree"] if
                              item["type"] == "blob" and item['path'].endswith((".yaml", ".yml"))]
                self.state.geo_rules[path["name"]] = yaml_files
        self.store.save_data('geo_rules', self.state.geo_rules)
        logger.info(
            f"Geo Rules 更新完成. 规则数量: {', '.join([f'{k}({len(v)})' for k, v in self.state.geo_rules.items()])}")

    def check_proxies_lifetime(self):
        for proxy in self.state.proxies_manager:
            proxy_name = proxy.proxy.name
            if proxy_name in self.state.overwritten_proxies:
                self.state.overwritten_proxies[proxy_name][
                    'lifetime'] = Crp.OVERWRITTEN_PROXIES_LIFETIME
        outdated_proxies = []
        for proxy_name, data in self.state.overwritten_proxies.items():
            if proxy_name not in self.state.proxies_manager:
                data['lifetime'] = data.get('lifetime', Crp.OVERWRITTEN_PROXIES_LIFETIME) - 1
                if data['lifetime'] < 0:
                    outdated_proxies.append(proxy_name)
        for proxy_name in outdated_proxies:
            del self.state.overwritten_proxies[proxy_name]
        self.store.save_data('overwritten_proxies', self.state.overwritten_proxies)
