# -*- coding:utf-8 -*-
"""
@Author  : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc    : 住户信息表 & 住户-房产关联表 增&删&改&查
"""
import csv
import os
import re
import shutil
from typing import Union, Optional, List

from pydantic import BaseModel, field_validator

from config import PREFIX_PATH, FACES_DIR_PATH, SUB_PATH
from config import APP_HOST as HOST_IP
from config import IMAGE_SERVER_PORT as PORT
from device.call import ServicesCall, DelFaceItem, AddFaceItem
from models.devices import DevicesTable
from models.houses import HousesTable
from utils import logger
from utils.database import BaseTable, get_table_handler
from utils.misc import now_datetime_second, InvalidException, is_image_valid, get_file_md5, encrypt_number, \
    decrypt_number

ROLE_TYPES = ["业主", "亲属", "租户"]


class HouseholdersInfo(BaseModel):
    householder_id: int
    name: str
    sex: str
    phone: str
    rooms: Union[str, list]
    add_datetime: str

    @field_validator("rooms")
    def convert_rooms(cls, value):
        return [i.strip() for i in value.split(",")]


class HouseholderDetailInfo(BaseModel):
    householder_id: int
    name: str
    phone: str
    face_url: str
    rooms: list
    authorized_devices: list


class AuthHouseholdersInfo(BaseModel):
    householder_id: int
    name: str
    phone: str
    rooms: Union[str, list]

    @field_validator("rooms")
    def convert_rooms(cls, value):
        return [i.strip() for i in value.split(",")]


class AuthUsersInfo(BaseModel):
    user_id: int
    name: str
    sex: str
    phone: str
    card_type: str
    card_id: str
    user_type: str
    start_datetime: str
    expire_datetime: str


class AuthUsersDetailInfo(AuthUsersInfo):
    face_url: str
    authorized_devices: List[dict]


class AddAuthUserInfo(BaseModel):
    name: str
    sex: str
    phone: str
    card_type: str
    card_id: str
    user_type: str
    start_datetime: str
    expire_datetime: str
    face_url: str
    authorized_devices: List[int]

    @field_validator("sex")
    def check_sex(cls, value):
        types = ["男", "女"]
        if value not in types:
            raise InvalidException(f"请提供正确的sex类型 {types}")
        else:
            return value

    @field_validator('phone')
    def check_phone(cls, value):
        pattern = re.compile(r'^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$')
        if pattern.search(value) is None:
            raise InvalidException("请提供正确的手机号码")
        th = get_table_handler()
        if HouseholdersTable.exists_user_phone(th, value):
            raise InvalidException("手机号码已存在，请勿重复提交")
        return value

    @field_validator("user_type")
    def check_user_type(cls, value):
        types = ["员工", "访客", "其他"]
        if value not in types:
            raise InvalidException(f"请提供正确的user_type类型 {types}")
        else:
            return value

    @field_validator("face_url")
    def check_face_url(cls, value, values):
        filename = value.split('/')[-1]
        file_path = str(os.path.join(FACES_DIR_PATH, filename))
        if is_image_valid(file_path):
            if filename.startswith('_'):
                new_filename = \
                    f"{values.data.get('name')}{encrypt_number(values.data.get('phone'))}.{filename.split('.')[-1]}"
                shutil.copy(file_path, f"{FACES_DIR_PATH}/{new_filename}")
                os.remove(file_path)
                return new_filename
            return filename
        else:
            raise InvalidException(f"地址或图片无效，请提供有效的图片地址 {value}")

    @field_validator("authorized_devices")
    def check_authorized_devices(cls, value):
        if len(value) > 0:
            return value
        else:
            raise InvalidException("请提供至少一个门禁设备权限")


class UpdateAuthUserInfo(AddAuthUserInfo):
    user_id: int
    phone: str

    @field_validator("user_id")
    def check_user_id(cls, value):
        th = get_table_handler()
        if HouseholdersTable.exists_user_id(th, value):
            return value
        else:
            raise InvalidException("user_id 不存在")

    @field_validator('phone')
    def check_phone(cls, value):
        pattern = re.compile(r'^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$')
        if pattern.search(value) is None:
            raise InvalidException("请提供正确的手机号码")
        return value


class AddHouseholderInfo(BaseModel):
    name: str
    sex: str
    phone: str
    property_info: List[tuple[str, str]]

    @field_validator('sex')
    def check_sex(cls, value):
        if value not in ["男", "女"]:
            raise InvalidException("请提供正确的性别类型")
        return value

    @field_validator('phone')
    def check_phone(cls, value):
        pattern = re.compile(r'^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$')
        if pattern.search(value) is None:
            raise InvalidException("请提供正确的手机号码")
        th = get_table_handler()
        if HouseholdersTable.exists_householder_phone(th, value):
            raise InvalidException("住户手机号码已存在，请勿重复提交")
        return value

    @field_validator('property_info')
    def check_property_info(cls, value):
        th = get_table_handler()
        room_ids = []
        for item in value:
            if not HousesTable.exists(th, item[0]):
                raise InvalidException("对应房产不存在")
            if item[1] not in ROLE_TYPES:
                raise InvalidException("请提供正确的身份类型")
            room_ids.append(item[0])
        if len(set(room_ids)) != len(room_ids):
            raise InvalidException("不能存在重复房产")
        return value


class GetHouseholdersInfo(BaseModel):
    search_type: Optional[str] = None
    search_key: Optional[str] = None
    role_type: str = "全部"
    limit: Optional[int] = None
    page: Optional[int] = None

    @field_validator("search_type")
    def check_search_type(cls, value):
        types = {
            "姓名": "name",
            "电话号码": "phone"
        }
        if value not in ["姓名", "电话号码"]:
            raise InvalidException("请提供正确的关键字类型")
        else:
            return types[value]

    @field_validator("search_key")
    def check_search_key(cls, value, values):
        if value and values.data.get("search_type", None):
            return value
        else:
            raise InvalidException("search 参数缺失")

    @field_validator("page")
    def check_page(cls, value, values):
        if (value is not None and value > 0
                and values.data.get("limit", None) is not None and values.data.get("limit") != 0):
            return value
        else:
            raise InvalidException("page 参数缺失或异常")

    @field_validator("role_type")
    def check_role_type(cls, value):
        if value not in ROLE_TYPES + ["全部"]:
            raise InvalidException("请提供正确的身份类型")
        else:
            return value


class GetAuthHouseholdersInfo(BaseModel):
    search_type: Optional[str] = None
    search_key: Optional[str] = None
    space_type: Optional[str] = None
    space_id: Optional[str] = None
    limit: Optional[int] = None
    page: Optional[int] = None

    @field_validator("search_type")
    def check_search_type(cls, value):
        types = {
            "住户姓名": "name",
            "手机号码": "phone"
        }
        if value not in types.keys():
            raise InvalidException(f"请提供正确的关键字类型 {list(types.keys())}")
        else:
            return types[value]

    @field_validator("search_key")
    def check_search_key(cls, value, values):
        if (value or value.strip() == "") and values.data.get("search_type", None):
            if value.strip() == "":
                value = None
            return value
        else:
            raise InvalidException("search 参数缺失")

    @field_validator("space_type")
    def check_space_type(cls, value):
        types = {
            "区域": "name_id",
            "楼栋": "building_id",
            "单元": "unit_id",
            "楼层": "floor_id",
            "房间号": "room_id"
        }
        if value not in types.keys():
            raise InvalidException(f"请提供正确的关联房产类型 {list(types.keys())}")
        else:
            return types[value]

    @field_validator("space_id")
    def check_space_id(cls, value, values):
        if value and values.data.get("space_type", None):
            return value
        else:
            raise InvalidException("space 参数缺失")

    @field_validator("page")
    def check_page(cls, value, values):
        if (value is not None and value > 0
                and values.data.get("limit", None) is not None and values.data.get("limit") != 0):
            return value
        else:
            raise InvalidException("page 参数缺失或异常")


class GetAuthUsersInfo(BaseModel):
    search_type: Optional[str] = None
    search_key: Optional[str] = None
    user_type: Optional[str] = None
    limit: Optional[int] = None
    page: Optional[int] = None

    @field_validator("search_type")
    def check_search_type(cls, value):
        types = {
            "姓名": "name",
            "手机号码": "phone"
        }
        if value not in types.keys():
            raise InvalidException(f"请提供正确的关键字类型 {list(types.keys())}")
        else:
            return types[value]

    @field_validator("search_key")
    def check_search_key(cls, value, values):
        if value and values.data.get("search_type", None):
            return value
        else:
            raise InvalidException("search 参数缺失")

    @field_validator("user_type")
    def check_space_type(cls, value):
        types = {
            "员工": "员工",
            "访客": "访客",
            "其他": "其他"
        }
        if value not in types.keys():
            raise InvalidException(f"请提供正确的人员类型 {list(types.keys())}")
        else:
            return types[value]

    @field_validator("page")
    def check_page(cls, value, values):
        if (value is not None and value > 0
                and values.data.get("limit", None) is not None and values.data.get("limit") != 0):
            return value
        else:
            raise InvalidException("page 参数缺失或异常")


class UpdateHouseholderInfo(BaseModel):
    householder_id: int
    property_info: List[tuple[str, str]]

    @field_validator('property_info')
    def check_property_info(cls, value):
        th = get_table_handler()
        for item in value:
            if not HousesTable.exists(th, item[0]):
                raise InvalidException("对应房产不存在")
            if item[1] not in ROLE_TYPES:
                raise InvalidException("请提供正确的身份类型")
        return value


class UpdateHouseholderFace(BaseModel):
    id: int
    face_url: str

    @field_validator("id")
    def check_id(cls, value):
        th = get_table_handler()
        if HouseholdersTable.exists_id(th, value):
            return value
        else:
            raise InvalidException(f"无效ID {value}")

    @field_validator("face_url")
    def check_face_url(cls, value, values):
        filename = value.split('/')[-1]
        file_path = str(os.path.join(FACES_DIR_PATH, filename))
        if is_image_valid(file_path):
            # 若为临时图片，则进行规则命名
            if filename.startswith('_'):
                th = get_table_handler()
                name_phone = HouseholdersTable.get_householder_np_by_id(th, values.data.get('id'))
                new_filename = f"{name_phone['name']}{name_phone['phone']}.{filename.split('.')[-1]}"
                shutil.copy(file_path, f"{FACES_DIR_PATH}/{new_filename}")
                os.remove(file_path)
                return new_filename
            return filename
        else:
            raise InvalidException(f"地址或图片无效，请提供有效的图片地址 {value}")


class HouseholdersTable(BaseTable):
    @staticmethod
    def check(table_handler: BaseTable):
        """检测是否存在当前表"""
        # householders 住户信息表
        table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='householders'")
        if table_handler.cursor.fetchone() is None:
            table_handler.execute(
                """
                CREATE TABLE householders (
                    householder_id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT,
                    sex TEXT,
                    phone TEXT,
                    face_url TEXT DEFAULT '',
                    face_md5 TEXT,
                    type TEXT,
                    user_type TEXT,
                    card_type TEXT,
                    card_id TEXT,
                    auth_start TEXT,
                    auth_expire TEXT,
                    add_datetime TEXT,
                    update_datetime TEXT,
                    UNIQUE (phone, type)
                )
                """
            )
        init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")),
                                        "data/InitialData/householders.csv")
        if os.path.exists(init_config_path):
            with open(init_config_path, newline='', encoding='utf8') as csvfile:
                csvreader = csv.reader(csvfile)
                head = next(csvreader)
                data = []
                if len(head) == 11:
                    for row in csvreader:
                        householder_id = row[0].strip()
                        name = row[1].strip()
                        sex = row[2].strip()
                        phone = encrypt_number(row[3].strip())
                        face_url = row[4].strip()
                        if face_url and os.path.exists(f"{FACES_DIR_PATH}/{face_url}"):
                            face_md5 = get_file_md5(f"{FACES_DIR_PATH}/{face_url}")
                        else:
                            face_md5 = None
                        _type = row[5].strip()
                        if _type != "住户":
                            user_type = row[6].strip()
                            card_type = row[7].strip()
                            card_id = row[8].strip()
                            auth_start = row[9].strip()
                            auth_expire = row[10].strip()
                        else:
                            user_type, card_type, card_id, auth_start, auth_expire = None, None, None, None, None
                        data.append((householder_id, name, sex, phone, face_url, face_md5,
                                     _type, user_type, card_type, card_id, auth_start, auth_expire,
                                     now_datetime_second(), now_datetime_second()))
                    table_handler.executemany(
                        """
                        INSERT OR IGNORE INTO householders 
                        (householder_id, name, sex, phone, face_url, face_md5,
                        type, user_type, card_type, card_id, auth_start, auth_expire,
                        add_datetime, update_datetime) 
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """, data
                    )

        # householders_type 住户-房产关联表
        table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='householders_type'")
        if table_handler.cursor.fetchone() is None:
            table_handler.execute(
                """
                CREATE TABLE householders_type (
                    householder_id INT,
                    room_id TEXT,
                    type TEXT,
                    update_datetime TEXT,
                    UNIQUE (householder_id, room_id)
                )
                """
            )
        init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")),
                                        "data/InitialData/householders_type.csv")
        if os.path.exists(init_config_path):
            with open(init_config_path, newline='', encoding='utf8') as csvfile:
                csvreader = csv.reader(csvfile)
                head = next(csvreader)
                data = []
                count_info = {}
                if len(head) == 3:
                    for row in csvreader:
                        householder_id = row[0].strip()
                        room_id = row[1].strip()
                        _type = row[2].strip()
                        if not HouseholdersTable.exists_householder_type(table_handler, householder_id, room_id):
                            if room_id in count_info.keys():
                                count_info[room_id] += 1
                            else:
                                count_info[room_id] = 1
                        data.append((int(householder_id), room_id, _type, now_datetime_second()))
                    table_handler.executemany(
                        """
                        INSERT INTO householders_type 
                        (householder_id, room_id, type, update_datetime) 
                        VALUES (?, ?, ?, ?) 
                        ON CONFLICT (householder_id, room_id) DO NOTHING
                        """, data
                    )
                    for _id, _count in count_info.items():
                        old_count = HousesTable.get_householder_count(table_handler, _id)
                        HousesTable.update_householder_count(table_handler, _id, old_count + _count)

    @staticmethod
    def insert(table_handler: BaseTable, name: str, sex: str, phone: str, _type: str):
        try:
            table_handler.execute(
                """
                INSERT OR IGNORE INTO householders 
                (name, sex, phone, type, add_datetime, update_datetime) 
                VALUES (?, ?, ?, ?, ?, ?)
                """, (name, sex, encrypt_number(phone), _type, now_datetime_second(), now_datetime_second())
            )
        finally:
            table_handler.query(
                """
                SELECT householder_id
                FROM householders
                WHERE phone = ?
                """, (encrypt_number(phone),)
            )
            res = table_handler.cursor.fetchall()
            if res:
                return res[0][0]
            else:
                return None

    @staticmethod
    def insert_type(table_handler: BaseTable, room_id: str, householder_id: int, _type: str):
        table_handler.execute(
            """
            INSERT INTO householders_type
            (householder_id, room_id, type, update_datetime) 
            VALUES (?, ?, ?, ?)
            ON CONFLICT (householder_id, room_id) DO UPDATE 
            SET type = ?, update_datetime = ?
            """, (householder_id, room_id, _type, now_datetime_second(), _type, now_datetime_second())
        )

    @staticmethod
    def add_auth_user_info(table_handler: BaseTable, obj: AddAuthUserInfo):
        """插入通用人员信息 & 进行通用人员设备授权"""
        now_datetime = now_datetime_second()
        face_md5 = get_file_md5(f"{FACES_DIR_PATH}/{obj.face_url}")
        table_handler.execute(
            """
            INSERT OR IGNORE INTO householders 
            (name, sex, phone, face_url, face_md5, type, 
            user_type, card_type, card_id, 
            auth_start, auth_expire, add_datetime, update_datetime)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (obj.name, obj.sex, encrypt_number(obj.phone), obj.face_url, face_md5, "非住户",
             obj.user_type, obj.card_type, obj.card_id,
             obj.start_datetime, obj.expire_datetime, now_datetime, now_datetime)
        )
        user_id = HouseholdersTable.get_householder_id(table_handler, obj.phone, '非住户')
        failed_ids = []
        codes = []
        msgs = []
        if user_id:
            try:
                # 人脸下置，设备授权
                sc = ServicesCall()
                face_item = AddFaceItem(
                    user_id=user_id,
                    name=obj.name,
                    phone_number=obj.phone,
                    face_url=obj.face_url,
                    start_date=obj.start_datetime.replace(' ', 'T') + ':00.000Z',
                    expire_date=obj.expire_datetime.replace(' ', 'T') + ':00.000Z',
                    device_ids=""
                )
                for device_id in obj.authorized_devices:
                    face_item.device_ids = f"[{device_id}]"
                    _callback, code, msg = sc.add_face(device_id, face_item)
                    if _callback:
                        DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id,
                                                               face_item.start_date, face_item.expire_date)
                    else:
                        failed_ids.append(device_id)
                        codes.append(code)
                        msgs.append(msg)
            finally:
                if 0 < len(failed_ids):
                    table_handler.execute(
                        """
                        DELETE FROM householders 
                        WHERE householder_id = ?
                        """, (user_id,)
                    )
                    return {"status": False, "message": f"部分设备 - {failed_ids} 人脸授权失败，错误码：{codes}，{msgs}"}
        return {"status": True}

    @staticmethod
    def get_householder_id(table_handler: BaseTable, phone: str, _type: str) -> str:
        table_handler.query("SELECT householder_id FROM householders WHERE phone=? AND type=?",
                            (encrypt_number(phone), _type))
        res = table_handler.cursor.fetchall()
        if res:
            return res[0][0]
        else:
            raise None

    @staticmethod
    def get_householder_info(table_handler: BaseTable, search_type, search_key, role_type,
                             page: Optional[int] = None, limit: Optional[int] = None):
        sub_sql_list = []
        if role_type != "全部":
            sub_sql_list.append(f"types like '%{role_type}%'")
        if search_type is not None:
            if search_type == "name":
                sub_sql_list.append(f"name like '%{search_key}%'")
            else:
                sub_sql_list.append(f"{search_type} = '{search_key}'")

        if sub_sql_list:
            sub_sql = "WHERE " + " AND ".join(sub_sql_list)
        else:
            sub_sql = ""
        if page is not None and limit is not None:
            page_sql = f"LIMIT {limit} OFFSET {(page - 1) * limit}"
        else:
            page_sql = ""
        query_sql = f"""
            SELECT * FROM (
                SELECT h.householder_id,
                       h.name,
                       h.sex,
                       h.phone,
                       GROUP_CONCAT(houses.house_name) AS rooms,
                       GROUP_CONCAT(hh.type) AS types,
                       h.add_datetime
                FROM householders h
                    LEFT JOIN householders_type hh ON h.householder_id = hh.householder_id
                    LEFT JOIN houses ON hh.room_id = houses.room_id
                WHERE h.type = '住户'
                GROUP BY h.householder_id, h.name, h.sex, h.phone, h.add_datetime
            ) {sub_sql}
        """
        table_handler.query(f"SELECT COUNT(*) FROM ({query_sql})")
        total = table_handler.cursor.fetchall()[0][0]
        table_handler.query(f"{query_sql} {page_sql}")
        res = table_handler.cursor.fetchall()
        if res:
            householder_info = []
            for i in res:
                householder_info.append(HouseholdersInfo(
                    householder_id=i[0],
                    name=i[1],
                    sex=i[2],
                    phone=decrypt_number(i[3]),
                    rooms=i[4],
                    add_datetime=i[6]
                ).__dict__)
            return {"info": householder_info, "total": total}
        else:
            return {"info": [], "total": total}

    @staticmethod
    def get_auth_householder_info(table_handler: BaseTable,
                                  search_type: Optional[str], search_key: Optional[str],
                                  space_type: Optional[str], space_id: Optional[str],
                                  page: Optional[int] = None, limit: Optional[int] = None):
        sub_sql_list = []
        if search_type is not None and search_key is not None:
            if search_type == "name":
                sub_sql_list.append(f"{search_type} like '%{search_key}%'")
            else:
                sub_sql_list.append(f"{search_type} = '{search_key}'")
        if space_type is not None and space_id is not None:
            space_sub_sql = f"""
            AND h.householder_id IN (
                SELECT DISTINCT h1.householder_id
                FROM householders h1
                         LEFT JOIN householders_type hh1 ON h1.householder_id = hh1.householder_id
                         LEFT JOIN houses houses1 ON hh1.room_id = houses1.room_id
                WHERE houses1.{space_type} = '{space_id}'
            )
            """
        else:
            space_sub_sql = ""
        if sub_sql_list:
            sub_sql = "WHERE " + " AND ".join(sub_sql_list)
        else:
            sub_sql = ""
        if page is not None and limit is not None:
            page_sql = f"LIMIT {limit} OFFSET {(page - 1) * limit}"
        else:
            page_sql = ""
        query_sql = f"""
            SELECT * FROM (
                SELECT h.householder_id, h.name, h.phone, 
                    GROUP_CONCAT(houses.house_name || '（' || hh.type || '）') AS rooms
                FROM householders h
                    LEFT JOIN householders_type hh ON h.householder_id = hh.householder_id
                    LEFT JOIN houses ON hh.room_id = houses.room_id
                WHERE h.type = '住户' {space_sub_sql}
                GROUP BY h.householder_id, h.name, h.phone
            ) {sub_sql}
        """
        table_handler.query(f"SELECT COUNT(*) FROM ({query_sql})")
        total = table_handler.cursor.fetchall()[0][0]
        table_handler.query(f"{query_sql} {page_sql}")
        res = table_handler.cursor.fetchall()
        if res:
            householder_info = []
            for i in res:
                householder_info.append(AuthHouseholdersInfo(
                    householder_id=i[0],
                    name=i[1],
                    phone=decrypt_number(i[2]),
                    rooms=i[3]
                ).__dict__)
            return {"info": householder_info, "total": total}
        else:
            return {"info": [], "total": total}

    @staticmethod
    def get_auth_users_info(table_handler: BaseTable,
                            search_type: Optional[str], search_key: Optional[str], user_type: Optional[str],
                            page: Optional[int] = None, limit: Optional[int] = None):
        sub_sql_list = []
        if search_type is not None:
            if search_type == "name":
                sub_sql_list.append(f"{search_type} like '%{search_key}%'")
            else:
                sub_sql_list.append(f"{search_type} = '{search_key}'")
        if user_type is not None:
            sub_sql_list.append(f"user_type = '{user_type}'")
        if sub_sql_list:
            sub_sql = "AND " + " AND ".join(sub_sql_list)
        else:
            sub_sql = ""
        if page is not None and limit is not None:
            page_sql = f"LIMIT {limit} OFFSET {(page - 1) * limit}"
        else:
            page_sql = ""
        query_sql = f"""
            SELECT householder_id, name, sex, phone, card_type, card_id, user_type, auth_start, auth_expire
            FROM householders
            WHERE type != '住户' {sub_sql}
        """
        table_handler.query(f"SELECT COUNT(*) FROM ({query_sql})")
        total = table_handler.cursor.fetchall()[0][0]
        table_handler.query(f"{query_sql} {page_sql}")
        res = table_handler.cursor.fetchall()
        if res:
            householder_info = []
            for i in res:
                householder_info.append(AuthUsersInfo(
                    user_id=i[0], name=i[1], sex=i[2], phone=decrypt_number(i[3]),
                    card_type=i[4], card_id=i[5], user_type=i[6],
                    start_datetime=i[7], expire_datetime=i[8]
                ).__dict__)
            return {"info": householder_info, "total": total}
        else:
            return {"info": [], "total": total}

    @staticmethod
    def get_auth_user_info(table_handler: BaseTable, householder_id: int):
        query_sql = """
            SELECT householder_id, name, sex, phone, face_url, card_type, card_id, user_type, auth_start, auth_expire
            FROM householders
            WHERE type != '住户' AND householder_id = ?
        """
        table_handler.query(query_sql, (householder_id,))
        res = table_handler.cursor.fetchall()
        if res:
            householder_info = AuthUsersDetailInfo(
                user_id=res[0][0], name=res[0][1], sex=res[0][2], phone=decrypt_number(res[0][3]),
                face_url=f"http://{HOST_IP}:{PORT}{PREFIX_PATH}/{SUB_PATH}/{res[0][4]}" if res[0][4] else '',
                card_type=res[0][5], card_id=res[0][6], user_type=res[0][7],
                start_datetime=res[0][8], expire_datetime=res[0][9],
                authorized_devices=DevicesTable.get_auth_device_info(table_handler, str(res[0][0]))
            ).__dict__
            return householder_info
        else:
            raise InvalidException(f"无效ID {householder_id}")

    @staticmethod
    def get_room_ids(table_handler: BaseTable, householder_id):
        """获取住户的所有房产ID"""
        table_handler.query(
            """
            SELECT room_id
            FROM householders_type
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return [i[0] for i in res]
        else:
            return []

    @staticmethod
    def get_rooms_by_householder_id(table_handler: BaseTable, householder_id):
        """获取住户的所有房产ID"""
        table_handler.query(
            """
            SELECT h.house_name, type, ht.room_id
            FROM householders_type ht
            LEFT JOIN houses h ON h.room_id = ht.room_id
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return [list(i) for i in res]
        else:
            return []

    @staticmethod
    def get_householder_np_by_id(table_handler: BaseTable, householder_id: int):
        table_handler.query(
            """
            SELECT name, phone
            FROM householders
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return {"name": res[0][0], "phone": decrypt_number(res[0][1])}
        else:
            raise InvalidException(f"无效ID {householder_id}")

    @staticmethod
    def get_face_url(table_handler: BaseTable, householder_id: int):
        table_handler.query(
            """
            SELECT DISTINCT face_url
            FROM householders
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return res[0][0]
        else:
            return ""

    @staticmethod
    def get_face_md5(table_handler: BaseTable, householder_id: int):
        table_handler.query(
            """
            SELECT DISTINCT face_md5
            FROM householders
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return res[0][0]
        else:
            return ""

    @staticmethod
    def get_householder_info_by_id(table_handler: BaseTable, householder_id: int):
        table_handler.query(
            """
            SELECT name, sex, phone
            FROM householders
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            householder_info = {
                "householder_id": householder_id,
                "name": res[0][0],
                "sex": res[0][1],
                "phone": decrypt_number(res[0][2]),
                "property_info": []
            }
            table_handler.query(
                """
                SELECT householders_type.room_id, house_name, area_name, building_name, unit_name, 
                householders_type.type, householder_count
                FROM householders_type
                LEFT JOIN houses ON houses.room_id = householders_type.room_id
                WHERE householder_id = ?
                """, (householder_id,)
            )
            res = table_handler.cursor.fetchall()
            if res:
                for i in res:
                    householder_info["property_info"].append({
                        "room_id": i[0],
                        "room_name": i[1],
                        "area_name": i[2] if i[2] else "",
                        "building_name": i[3] if i[3] else "",
                        "unit_name": i[4] if i[4] else "",
                        "type": i[5],
                        "household_count": i[6]
                    })
            return householder_info
        return {}

    @staticmethod
    def get_auth_householder_detail_info(table_handler: BaseTable, householder_id: int):
        info = HouseholdersTable.get_householder_info_by_id(table_handler, householder_id)
        rooms = HouseholdersTable.get_rooms_by_householder_id(table_handler, info["householder_id"])
        face_url = HouseholdersTable.get_face_url(table_handler, householder_id)
        device_ids = DevicesTable.get_auth_device_ids(table_handler, str(householder_id))
        authorized_devices = []
        for device_id in device_ids:
            start_date, expire_date = DevicesTable.get_auth_interval(table_handler, str(householder_id), device_id)
            authorized_devices.append({
                "device_name": DevicesTable.get_device_name(table_handler, device_id),
                "device_type": DevicesTable.get_device_scope_type(table_handler, device_id),
                "open_type": ["人脸"],
                "start_date": start_date,
                "expire_date": expire_date,
                "method": "自动授权",
                "from": "系统"
            })

        resp = HouseholderDetailInfo(
            householder_id=info["householder_id"],
            name=info["name"],
            phone=info["phone"],
            face_url=f"http://{HOST_IP}:{PORT}{PREFIX_PATH}/{SUB_PATH}/{face_url}",
            rooms=rooms,
            authorized_devices=authorized_devices
        )
        return resp.__dict__

    @staticmethod
    def update_householder_info(table_handler: BaseTable,
                                householder_id: int,
                                property_info: List[tuple[str, str]]):
        # 查询对应住户关联的房产信息，比对更新数据中的房产信息
        update_room_ids = [i[0] for i in property_info]
        update_types = [i[1] for i in property_info]
        table_handler.query(
            """
            SELECT room_id, type
            FROM householders_type
            WHERE householder_id = ?
            """, (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            update_data = []
            delete_data = []
            insert_data = [[v, update_types[i]] for i, v in enumerate(update_room_ids)]
            sqls = []
            params = []
            update_count = []
            for i in res:
                try:
                    index = update_room_ids.index(i[0])
                    if update_types[index] != i[1]:
                        update_data.append([i[0], update_types[index]])
                    insert_data.remove([i[0], update_types[index]])
                except ValueError:
                    delete_data.append(i[0])
            table_handler.query(
                """
                SELECT name, phone, face_url, auth_start, auth_expire
                FROM householders
                WHERE householder_id = ?
                """, (householder_id,)
            )
            householder_info = table_handler.cursor.fetchall()[0]
            sc = ServicesCall()
            face_item = AddFaceItem(
                user_id=householder_id,
                name=householder_info[0],
                phone_number=householder_info[1],
                face_url=householder_info[2],
                start_date=householder_info[3].replace(' ', 'T') + ':00.000Z',
                expire_date=householder_info[4].replace(' ', 'T') + ':00.000Z',
                device_ids=""
            )
            if update_data:
                for data in update_data:
                    sqls.append(
                        """
                        UPDATE householders_type 
                        SET type = ?, update_datetime = ? 
                        WHERE householder_id = ? AND room_id = ?
                        """
                    )
                    params.append((data[1], now_datetime_second(), householder_id, data[0]))
            if insert_data:
                for data in insert_data:
                    sqls.append(
                        """
                        INSERT INTO householders_type 
                        (room_id, householder_id, type, update_datetime)
                        VALUES (?, ?, ?, ?)
                        """
                    )
                    params.append((data[0], householder_id, data[1], now_datetime_second()))
                    old_count = HousesTable.get_householder_count(table_handler, data[0])
                    update_count.append([data[0], old_count + 1])
                    # TODO 同步增加对应设备中的人脸信息
                    unit_id = HousesTable.get_unit_id_by_room_id(table_handler, data[0])
                    device_ids = DevicesTable.get_device_ids_by_unit_id(table_handler, unit_id)
                    for device_id in device_ids:
                        face_item.device_ids = f"[{device_id}]"
                        _callback, code, msg = sc.add_face(device_id, face_item)
                        if _callback:
                            DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id,
                                                                   face_item.start_date, face_item.expire_date)
                        logger.Logger.error(f"[update_householder_info] "
                                            f"住户[{householder_id}]人脸增加失败，失败代码：{code}，提示信息：{msg}")
            if delete_data:
                for room_id in delete_data:
                    sqls.append(
                        """
                        DELETE FROM householders_type 
                        WHERE householder_id = ? AND room_id = ?
                        """
                    )
                    params.append((householder_id, room_id))
                    old_count = HousesTable.get_householder_count(table_handler, room_id)
                    update_count.append([room_id, old_count - 1])
                    # TODO 获取房产-单元 同步移除对应设备中的人脸
                    unit_id = HousesTable.get_unit_id_by_room_id(table_handler, room_id)
                    device_ids = DevicesTable.get_device_ids_by_unit_id(table_handler, unit_id)
                    for device_id in device_ids:
                        _callback, code, msg = sc.del_face(device_id,
                                                           DelFaceItem(user_id=householder_id, device_ids=f"[{device_id}]"))
                        if _callback:
                            DevicesTable.delete_invalid_auth_record(table_handler, device_id, str(householder_id))
                        logger.Logger.error(f"[update_householder_info] "
                                            f"住户[{householder_id}]人脸移除失败，失败代码：{code}，提示信息：{msg}")
            if len(update_room_ids) == 0:
                sqls.append(
                    """
                    DELETE FROM householders 
                    WHERE householder_id = ?
                    """
                )
                params.append((householder_id,))
            table_handler.execute(sqls, params)
            for i in update_count:
                HousesTable.update_householder_count(table_handler, i[0], i[1])
            return {"status": True}
        else:
            return {"status": False, "message": "不存在对应住户或住户名下无关联房产"}

    @staticmethod
    def update_auth_user_info(table_handler: BaseTable, obj: UpdateAuthUserInfo):
        # 通用人员授权信息更新
        nd = now_datetime_second().replace(' ', 'T') + '.000Z'
        sd = obj.start_datetime.replace(' ', 'T') + ':00.000Z'
        ed = obj.expire_datetime.replace(' ', 'T') + ':00.000Z'
        sc = ServicesCall()
        face_item = AddFaceItem(
            user_id=obj.user_id,
            name=obj.name,
            phone_number=obj.phone,
            face_url=obj.face_url,
            start_date=sd,
            expire_date=ed,
            device_ids=""
        )
        old_face_url = HouseholdersTable.get_face_url(table_handler, obj.user_id)
        # 查询当前人员授权情况
        table_handler.query(
            """
            SELECT device_id, start_date, expire_date
            FROM devices_auth
            WHERE _id = ?
            """, (obj.user_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            delete_data = []  # 移除权限
            insert_data = [[device_id, sd, ed] for device_id in obj.authorized_devices]  # 新增权限
            for i in res:
                if old_face_url == obj.face_url:  # 对比人脸图片是否变更
                    # 人脸无实际变更，根据权限变更重新赋权
                    logger.Logger.debug("新旧人脸一致，按需赋权")
                    if [i[0], sd, ed] in insert_data and ed == i[2] and (sd < nd or sd == i[1]):
                        insert_data.remove([i[0], sd, ed])
                    else:
                        delete_data.append(i[0])
                else:
                    # 若人脸变更直接移除所有人脸授权，重新赋权
                    delete_data.append(i[0])
            delete_failed_ids = []
            delete_failed_codes = []
            delete_failed_msgs = []
            message = ""
            if delete_data:
                for device_id in delete_data:
                    _callback, code, msg = sc.del_face(device_id, DelFaceItem(
                        user_id=obj.user_id, device_ids=f"[{device_id}]"))
                    if _callback:
                        DevicesTable.delete_invalid_auth_record(table_handler, device_id, face_item.user_id)
                    else:
                        delete_failed_ids.append(device_id)
                        delete_failed_codes.append(code)
                        delete_failed_msgs.append(msg)
            if 0 < len(delete_failed_ids) < len(delete_data):
                message += f"部分设备 - {delete_failed_ids} 人脸移除失败，错误码：{delete_failed_codes}, {delete_failed_msgs}"
            elif len(delete_failed_ids) == len(delete_data) != 0:
                raise InvalidException("设备授权无法更新：人脸移除失败")
            insert_failed_ids = []
            insert_failed_codes = []
            if insert_data:
                for data in insert_data:
                    face_item.device_ids = f"[{data[0]}]"
                    _callback, code, msg = sc.add_face(data[0], face_item)
                    if _callback:
                        DevicesTable.insert_device_auth_record(table_handler, data[0], face_item.user_id,
                                                               face_item.start_date, face_item.expire_date)
                    else:
                        insert_failed_ids.append(data[0])
                        insert_failed_codes.append(code)
            if 0 < len(insert_failed_ids) < len(insert_data):
                message += f"部分设备 - {delete_failed_ids} 人脸授权失败，错误码：{delete_failed_codes}"
            elif len(delete_failed_ids) == len(delete_data) != 0:
                raise InvalidException("设备授权无法更新：人脸下发失败")
            if message != '':
                return InvalidException(message)
            else:
                table_handler.execute(
                    """
                    UPDATE householders
                    SET name = ?, sex = ?, phone= ?, 
                    face_url = ?, face_md5= ?,
                    user_type = ?, card_type = ?, card_id = ?,
                    auth_start = ?, auth_expire = ?, update_datetime = ?
                    WHERE householder_id = ?
                    """, (obj.name, obj.sex, encrypt_number(obj.phone),
                          obj.face_url, get_file_md5(f"{FACES_DIR_PATH}/{obj.face_url}"),
                          obj.user_type, obj.card_type, obj.card_id,
                          sd, ed, now_datetime_second(), obj.user_id)
                )
                return {"status": True}
        else:
            return {"status": False, "message": "不存在对应ID的授权"}

    @staticmethod
    def update_householder_face(table_handler: BaseTable, householder_id: int, face_url: str):
        """更新住户信息中的人脸地址 & 人脸地址同步到权限内的设备"""
        new_md5 = get_file_md5(f"{FACES_DIR_PATH}/{face_url}")
        if new_md5 != HouseholdersTable.get_face_md5(table_handler, householder_id):
            table_handler.execute(
                """
                UPDATE householders 
                SET face_url = ?, update_datetime = ? WHERE householder_id = ?
                """, (face_url, now_datetime_second(), householder_id)
            )
            # 查询住户关联单元，获取单元权限内的设备，下发人脸
            table_handler.query(
                """
                SELECT h.householder_id, name, phone, GROUP_CONCAT(unit_id) as unit_ids
                FROM householders h
                         LEFT JOIN householders_type ht ON ht.householder_id = h.householder_id
                         LEFT JOIN houses ON ht.room_id = houses.room_id
                WHERE h.type = '住户' AND h.householder_id = ?
                """, (householder_id,)
            )
            res = table_handler.cursor.fetchall()
            if res:
                sc = ServicesCall()
                face_item = AddFaceItem(
                    user_id=res[0][0],
                    name=res[0][1],
                    phone_number=decrypt_number(res[0][2]),
                    face_url=face_url,
                    device_ids=""
                )
                for unit_id in res[0][3].split(','):
                    device_ids = DevicesTable.get_device_ids_by_unit_id(table_handler, unit_id)
                    for device_id in device_ids:
                        face_item.device_ids = f"[{device_id}]"
                        _callback, code, msg = sc.add_face(device_id, face_item)
                        if _callback:
                            DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id,
                                                                   face_item.start_date, face_item.expire_date)
            return {"status": True}
        else:
            logger.Logger.debug("新旧人脸一致，不做任何变更")
            return {"status": True}

    @staticmethod
    def update_user_info(table_handler: BaseTable, householder_id: int, face_url: str):
        """更新通用人员信息中的人脸地址 & 人脸地址同步到下发过的设备中"""
        table_handler.execute(
            """
            UPDATE householders 
            SET face_url = ? WHERE householder_id = ?
            """, (face_url, householder_id)
        )
        # 查询人员授权过的设备，下发人脸
        device_ids = DevicesTable.get_auth_device_ids(table_handler, str(householder_id))
        name_phone = HouseholdersTable.get_householder_np_by_id(table_handler, householder_id)
        sc = ServicesCall()
        face_item = AddFaceItem(
            user_id=householder_id,
            name=name_phone["name"],
            phone_number=name_phone["phone"],
            face_url=face_url,
            device_ids=""
        )
        for device_id in device_ids:
            face_item.device_ids = f"[{device_id}]"
            _callback, code, _ = sc.add_face(device_id, face_item)
            if _callback:
                DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id,
                                                       face_item.start_date, face_item.expire_date)
        return {"status": True}

    @staticmethod
    def delete_householder_info(table_handler: BaseTable, householder_id: int):
        # 确认住户ID有效
        if not HouseholdersTable.exists_householder_id(table_handler, householder_id):
            raise InvalidException("不存在对应住户ID")
        # 1. 查询当前住户授权的授权的设备ID
        device_ids = DevicesTable.get_auth_device_ids(table_handler, str(householder_id))
        # 2. 对所有授权设备进行授权移除操作
        if device_ids:
            sc = ServicesCall()
            _callback, code, msg = sc.del_face(device_ids[0], DelFaceItem(
                user_id=str(householder_id),
                device_ids=str(device_ids)
            ))
            if not _callback:
                raise InvalidException(f"住户人脸授权移除失败，错误码{code}，{msg}")
            DevicesTable.delete_householder_all_auth(table_handler, str(householder_id))
        # 3. 同步更新住户名下房产入住人数，并移除住户记录
        room_ids = HouseholdersTable.get_room_ids(table_handler, householder_id)
        for room_id in room_ids:
            old_count = HousesTable.get_householder_count(table_handler, room_id)
            HousesTable.update_householder_count(table_handler, room_id, old_count - 1)
        sqls = [
            "DELETE FROM householders WHERE householder_id = ?",
            "DELETE FROM householders_type WHERE householder_id = ?"
        ]
        table_handler.execute(sqls, [(householder_id,), (householder_id,)])
        return {"status": True}

    @staticmethod
    def delete_auth_user_info(table_handler: BaseTable, user_id: int):
        # 确认通用用户ID有效
        if not HouseholdersTable.exists_user_id(table_handler, user_id):
            raise InvalidException("不存在对应通用用户ID")
        # 1. 查询当前住户授权的授权的设备ID
        device_ids = DevicesTable.get_auth_device_ids(table_handler, str(user_id))
        # 2. 对所有授权设备进行授权移除操作
        if device_ids:
            sc = ServicesCall()
            _callback, code, msg = sc.del_face(device_ids[0], DelFaceItem(
                user_id=str(user_id),
                device_ids=str(device_ids)
            ))
            if not _callback:
                raise InvalidException(f"住户人脸授权移除失败，错误码{code}，{msg}")
            DevicesTable.delete_householder_all_auth(table_handler, str(user_id))
        sqls = [
            "DELETE FROM householders WHERE householder_id = ?",
            "DELETE FROM householders_type WHERE householder_id = ?"
        ]
        table_handler.execute(sqls, [(user_id,), (user_id,)])
        return {"status": True}

    @staticmethod
    def delete_type(table_handler: BaseTable, householder_id: int):
        table_handler.execute(
            """
            DELETE FROM householders_type
            WHERE householder_id = ?
            """, (householder_id,)
        )

    @staticmethod
    def exists_householder_phone(table_handler: BaseTable, phone: str) -> bool:
        """查询住户手机号码是否存在"""
        table_handler.query(
            """
            SELECT householder_id 
            FROM householders 
            WHERE type = '住户' AND phone = ?
            """, (encrypt_number(phone),)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return True
        else:
            return False

    @staticmethod
    def exists_user_phone(table_handler: BaseTable, phone: str):
        """查询非住户手机号码是否存在"""
        table_handler.query(
            """
            SELECT householder_id 
            FROM householders 
            WHERE type != '住户' AND phone = ?
            """, (encrypt_number(phone),)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return True
        else:
            return False

    @staticmethod
    def exists_id(table_handler: BaseTable, householder_id: int) -> bool:
        """查询ID是否存在"""
        table_handler.query(
            """
            SELECT householder_id 
            FROM householders 
            WHERE householder_id = ?
            """,
            (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return True
        else:
            return False

    @staticmethod
    def exists_householder_id(table_handler: BaseTable, householder_id: int) -> bool:
        """查询住户ID是否存在"""
        table_handler.query(
            """
            SELECT householder_id 
            FROM householders 
            WHERE type = '住户' AND householder_id = ? 
            """,
            (householder_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return True
        else:
            return False

    @staticmethod
    def exists_user_id(table_handler: BaseTable, user_id: int) -> bool:
        """查询非住户ID是否存在"""
        table_handler.query(
            """
            SELECT householder_id 
            FROM householders 
            WHERE  type != '住户' AND householder_id = ?
            """,
            (user_id,)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return True
        else:
            return False

    @staticmethod
    def exists_householder_type(table_handler: BaseTable, householder_id: str, room_id: str) -> bool:
        """查询住户-房产关联信息是否存在"""
        table_handler.query(
            """
            SELECT householder_id 
            FROM householders_type 
            WHERE householder_id = ? AND room_id = ?
            """,
            (householder_id, room_id)
        )
        res = table_handler.cursor.fetchall()
        if res:
            return True
        else:
            return False
