DROP DATABASE IF EXISTS cardManageSystem;
CREATE DATABASE cardManageSystem;
ALTER DATABASE cardManageSystem CHARACTER SET utf8;

DROP USER IF EXISTS 'cardManageSystem'@'%';
CREATE USER 'cardManageSystem'@'%' IDENTIFIED BY 'RFID666';

USE cardManageSystem;

DROP PROCEDURE IF EXISTS sp_depositCard;
DROP PROCEDURE IF EXISTS sp_consumeCard;

CREATE TABLE device (
    id INT AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(255) UNIQUE,
    depositAllowed TINYINT NOT NULL,
    CONSTRAINT chk_device_depositAllowed CHECK (depositAllowed IN (0, 1))
) AUTO_INCREMENT=1000;

CREATE TABLE `user` (
    id INT PRIMARY KEY,
    `name` VARCHAR(255) NOT NULL,
    `password` VARCHAR(255) NOT NULL
);

CREATE TABLE card (
    id VARCHAR(16) PRIMARY KEY,
    `status` TINYINT NOT NULL,
    balance DECIMAL(6, 2) NOT NULL,
    userId INT,
    FOREIGN KEY (userId) REFERENCES `user`(id),
    CONSTRAINT chk_card_status CHECK (`status` IN (-1, 0, 1)),
    CONSTRAINT chk_card_balance CHECK (balance >= 0)
);

CREATE TABLE record (
    id VARCHAR(255) PRIMARY KEY,
    cardId VARCHAR(16) NOT NULL,
    `time` DATETIME NOT NULL,
    `type` TINYINT NOT NULL,
    `value` DECIMAL(6, 2) NOT NULL,
    originalBalance DECIMAL(6, 2) NOT NULL,
    balance DECIMAL(6, 2) NOT NULL,
    deviceId INT NOT NULL,
    FOREIGN KEY (cardId) REFERENCES card(id),
    FOREIGN KEY (deviceId) REFERENCES device(id),
    CONSTRAINT chk_value CHECK (`value` >= -300),
    CONSTRAINT chk_record CHECK (originalBalance + `value` = balance),
    CONSTRAINT chk_record_originalBalance CHECK (originalBalance >= 0),
    CONSTRAINT chk_record_balance CHECK (balance >= 0),
    CONSTRAINT chk_record_type CHECK (`type` IN (0, 1))
);


DELIMITER //

CREATE PROCEDURE sp_depositCard(
    IN p_cardId VARCHAR(16),
    IN p_amount DECIMAL(6, 2),
    IN p_recordId VARCHAR(255),
    IN p_deviceId INT,
    IN p_time DATETIME,
    IN p_type TINYINT,
    OUT p_newBalance DECIMAL(6, 2)
)
BEGIN
    DECLARE v_originalBalance DECIMAL(6, 2);
    

    SELECT balance INTO v_originalBalance
    FROM card
    WHERE id = p_cardId;
    
    IF v_originalBalance IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Card not found';
    END IF;


    SET p_newBalance = v_originalBalance + p_amount;
    
    
    UPDATE card
    SET balance = p_newBalance
    WHERE id = p_cardId;
    
    
    INSERT INTO record (id, cardId, `time`, `type`, `value`, originalBalance, balance, deviceId)
    VALUES (p_recordId, p_cardId, p_time, p_type, p_amount, v_originalBalance, p_newBalance, p_deviceId);
    
END //


CREATE PROCEDURE sp_consumeCard(
    IN p_cardId VARCHAR(18),
    IN p_amount DECIMAL(6, 2),
    IN p_recordId VARCHAR(255),
    IN p_deviceId INT,
    IN p_time DATETIME,
    IN p_type TINYINT,
    OUT p_newBalance DECIMAL(6, 2)
)
BEGIN
    DECLARE v_originalBalance DECIMAL(6, 2);

    IF p_amount > 300 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Amount';
    END IF;
    
    
    SELECT balance INTO v_originalBalance
    FROM card
    WHERE id = p_cardId;
    
    IF v_originalBalance IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Card not found';
    END IF;
    
    
    SET p_newBalance = v_originalBalance - p_amount;
    
    
    IF p_newBalance < 0 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient balance';
    END IF;
    
    
    UPDATE card
    SET balance = p_newBalance
    WHERE id = p_cardId;

    
    INSERT INTO record (id, cardId, `time`, `type`, `value`, originalBalance, balance, deviceId)
    VALUES (p_recordId, p_cardId, p_time, p_type, -p_amount, v_originalBalance, p_newBalance, p_deviceId);
    
END //

DELIMITER ;


CREATE VIEW record_view AS
SELECT time, type, value, balance, `name` AS device, record.id AS id, cardId
FROM record
JOIN device
ON deviceId = device.id
ORDER BY time DESC, type DESC;


INSERT INTO device (`name`, depositAllowed)
VALUES ('食堂1号机', 0), ('管理员1号机', 1);


GRANT ALL PRIVILEGES ON cardManageSystem.* TO 'cardManageSystem'@'%';

FLUSH PRIVILEGES;