set serveroutput on size 32000
-- show errors;

-- Question 1: Retailer detail
create or replace procedure RetailerDetail (id IN Retailers.RetailerId%TYPE) as
retailer_id Retailers.RetailerId%TYPE;
retailer_name Retailers.RetailerName%TYPE;
retailer_address Retailers.Address%TYPE;
retailer_orders Orders.Count%TYPE;
retailer_bestseller_id Products.ProductId%TYPE;
retailer_bestseller Products.ProductName%TYPE;
retailer_bestseller_count Orders.Count%TYPE;
-- CURSOR retailer_cursor IS SELECT RetailerId FROM Retailers WHERE RetailerId=id;
-- retailer_entry retailer_cursor%ROWTYPE;
CURSOR order_cursor IS SELECT Count, OrderId FROM Orders WHERE RetailerId=id;
order_entry order_cursor%ROWTYPE;
BEGIN
	SELECT RetailerName
	INTO retailer_name
	FROM Retailer
	WHERE RetailerId=id;

	SELECT Address
	INTO retailer_address
	FROM Retailer
	WHERE RetailerId=id;

	SELECT COUNT(OrderId)
	INTO retailer_orders
	FROM Orders
	WHERE RetailerId=id;

	SELECT ProductId
	INTO retailer_bestseller_id
	FROM (
		SELECT ProductId, RANK()
        OVER (ORDER BY Total DESC) AS Score
		FROM (
			SELECT ProductId, SUM(Count) AS Total
			FROM Orders O
			WHERE RetailerId=id
			GROUP BY ProductId
		)
	)
	WHERE Score = 1
	GROUP BY ProductId;

	SELECT ProductName
	INTO retailer_bestseller
	FROM Products
	WHERE ProductId=retailer_bestseller_id;

	SELECT SUM(Count)
	INTO retailer_bestseller_count
	FROM Orders
	WHERE ProductId=retailer_bestseller_id AND RetailerId=id;

	dbms_output.put_line('Retailer Name: ' || retailer_name);
	dbms_output.put_line('Retailer Address: ' || retailer_address);
	dbms_output.put_line('Retailer Total Orders: ' || retailer_orders);
	dbms_output.put_line('Most Popular Product: ' || retailer_bestseller);
	dbms_output.put_line('Total Sold: ' || retailer_bestseller_count);
END RetailerDetail;
/

-- show error procedure RetailerDetail;

-- make test cases yourselves
BEGIN
RetailerDetail(1); -- Inferno, 115
RetailerDetail(2); -- Macbook, 12
RetailerDetail(3); -- Nike sports jacket, 3
RetailerDetail(4); -- OnePlus 5t, 1
RetailerDetail(5); -- Pixel Book, 1
end;
/

-- Queation 2: Monthly delay report
create or replace procedure MonthlyDelayReport as
	order_date VARCHAR(32);
	order_count Orders.Count%TYPE;
	order_year VARCHAR(32);
	order_month VARCHAR(32);
	retailer_name Retailers.RetailerName%TYPE;
	CURSOR c_month IS
		SELECT Year, Month, Total
		FROM (
			SELECT Year, Month, COUNT(OrderId) AS Total
			FROM (
				-- EXTRACT
				SELECT EXTRACT(YEAR FROM OrderDate) AS Year, EXTRACT(MONTH FROM OrderDate) AS Month, OrderId, Status
				FROM Orders
			)
			WHERE Status = 'DELAYED'
			GROUP BY Year, Month
			ORDER BY Year ASC, Month ASC
		)
		WHERE Total > 0;
	CURSOR c_retailer IS
		SELECT DISTINCT R.RetailerName
		INTO retailer_name
		FROM (
			SELECT RetailerId
			FROM Orders
			WHERE EXTRACT(YEAR FROM OrderDate) = order_year AND EXTRACT(MONTH FROM OrderDate) = order_month AND Status = 'DELAYED'
		) K, Retailers R
		WHERE R.RetailerId = K.RetailerId
		ORDER BY R.RetailerName ASC;
BEGIN
	OPEN c_month;

	LOOP
	FETCH c_month INTO order_year, order_month, order_count;
		EXIT WHEN c_month%NOTFOUND;
		dbms_output.put_line('Delayed orders in ' || order_year || '-' || order_month || ': ' || order_count);

		dbms_output.put_line('Retailers with delayed orders:');
		OPEN c_retailer;

		LOOP
		FETCH c_retailer INTO retailer_name;
			EXIT WHEN c_retailer%NOTFOUND;
			dbms_output.put_line('- ' || retailer_name);
		END LOOP;

		CLOSE c_retailer;
	END LOOP;

	CLOSE c_month;
END MonthlyDelayReport;
/

-- show error procedure MonthlyDelayReport;

BEGIN
	MonthlyDelayReport;
End;
/

-- Question 3: Find the product with least profit in each category
create or replace procedure LeastProfitProduct as
	product_category Products.Category%TYPE;
	product_name Products.ProductName%TYPE;
	product_profit Products.ExfactoryPrice%TYPE;
	CURSOR c_profit IS
		SELECT ProductName, AverageProfit
		FROM (
			SELECT P.ProductName, AverageProfit, RANK()
				OVER (ORDER BY AverageProfit ASC) AS Score
			FROM (
				SELECT ProductId, AVG(ProfitMargin) AS AverageProfit
				FROM (
					SELECT O.OrderId, O.ProductId, O.UnitPrice - P.ExFactoryPrice AS ProfitMargin
					FROM Orders O, Products P
					WHERE O.ProductId = P.ProductId AND P.Category = product_category
				)
				GROUP BY ProductId
			) K, Products P
			WHERE K.ProductId = P.ProductId
			ORDER BY AverageProfit DESC, P.ProductName ASC
		)
		WHERE Score = 1;
	CURSOR c_category IS
		SELECT DISTINCT Category
		FROM Products;
BEGIN
	OPEN c_category;

	LOOP
	FETCH c_category INTO product_category;
		EXIT WHEN c_category%NOTFOUND;
		dbms_output.put_line('Least Profit in ' || product_category);

		OPEN c_profit;

		LOOP
		FETCH c_profit INTO product_name, product_profit;
			EXIT WHEN c_profit%NOTFOUND;
			dbms_output.put_line('- ' || product_name || ': ' || product_profit);
		END LOOP;

		CLOSE c_profit;
	END LOOP;

	CLOSE c_category;
END LeastProfitProduct;
/

BEGIN
	LeastProfitProduct;
END;
/

-- Queation 4: New table for retailer product category distribution
create table RetailerCatergoryTable(RetailerId integer, Electronic integer, Apparel integer, Books integer, primary key(RetailerId));
create or replace procedure RetailerProductCatergory as
	product_category Products.Category%TYPE;
	retailer_id Retailers.RetailerId%TYPE;
	total_electronic integer;
	total_apparel integer;
	total_books integer;
	CURSOR c_retailer IS
		SELECT RetailerId
		FROM Retailers;
	CURSOR c_category IS
		SELECT COUNT(OrderId) AS Total
		FROM Orders O, Products P
		WHERE O.ProductId = P.ProductId AND P.Category = product_category AND O.RetailerId = retailer_id;
BEGIN
	OPEN c_retailer;

	LOOP
	FETCH c_retailer INTO retailer_id;
		EXIT WHEN c_retailer%NOTFOUND;

		product_category := 'electronics';
		SELECT COUNT(OrderId)
		INTO total_electronic
		FROM Orders O, Products P
		WHERE O.ProductId = P.ProductId AND P.Category = product_category AND O.RetailerId = retailer_id;

		product_category := 'apparel';
		SELECT COUNT(OrderId)
		INTO total_apparel
		FROM Orders O, Products P
		WHERE O.ProductId = P.ProductId AND P.Category = product_category AND O.RetailerId = retailer_id;

		product_category := 'books';
		SELECT COUNT(OrderId)
		INTO total_books
		FROM Orders O, Products P
		WHERE O.ProductId = P.ProductId AND P.Category = product_category AND O.RetailerId = retailer_id;

		INSERT INTO RetailerCatergoryTable (RetailerId, Electronic, Apparel, Books) VALUES (retailer_id, total_electronic, total_apparel, total_books);
	END LOOP;

	CLOSE c_retailer;
END RetailerProductCatergory;
/

-- show error procedure RetailerProductCatergory;

BEGIN
RetailerProductCatergory;
END;
/
select * from RetailerCatergoryTable;

drop table RetailerCatergoryTable;


-- Question 5: Exception Handle
create or replace procedure CustomerProductInfo(cid IN Customers.CustomerId%TYPE, pid IN Products.ProductId%TYPE) as
	invalid_input EXCEPTION;
	order_date Orders.OrderDate%TYPE;
	check_cid Customers.CustomerId%TYPE;
	check_pid Products.ProductId%TYPE;
	CURSOR c_customer_product IS
		SELECT DISTINCT O.OrderDate
		FROM Orders O, Products P
		WHERE O.ProductId = pid AND O.CustomerId = cid;
BEGIN
	OPEN c_customer_product;

	dbms_output.put_line('Records of customer id ' || cid || ' with product id ' || pid || ':');

	SELECT CustomerId
	INTO check_cid
	FROM Customers
	WHERE CustomerId = cid;

	SELECT ProductId
	INTO check_pid
	FROM Products
	WHERE ProductId = pid;

	LOOP
	FETCH c_customer_product INTO order_date;
		EXIT WHEN c_customer_product%NOTFOUND;

		dbms_output.put_line('OrderDate: ' || order_date);
	END LOOP;

	CLOSE c_customer_product;
EXCEPTION
	WHEN no_data_found THEN
		dbms_output.put_line('Invalid customer id or product id!');
	WHEN others THEN
		dbms_output.put_line('Invalid customer id or product id!');
END CustomerProductInfo;
/

-- show error procedure CustomerProductInfo;

BEGIN
CustomerProductInfo(1,1);
CustomerProductInfo(-1,1);
CustomerProductInfo(1,-1);
CustomerProductInfo(-1,-1);
CustomerProductInfo(100,100);
CustomerProductInfo(4,3);
CustomerProductInfo(2,5);
CustomerProductInfo(7,11);
CustomerProductInfo(8,9);
CustomerProductInfo(9,5);
END;
/
