//! SYN-721 NFT Contract
//!
//! A standard non-fungible token implementation for Synor, similar to ERC-721.
//!
//! # Features
//! - Minting NFTs with metadata URI
//! - Burning
//! - Transfers
//! - Approvals (per-token and operator)
//! - Ownership management
//!
//! # Methods
//! - `init(name, symbol)` - Initialize NFT collection
//! - `name() -> String` - Collection name
//! - `symbol() -> String` - Collection symbol
//! - `total_supply() -> u64` - Total minted tokens
//! - `balance_of(owner) -> u64` - Token count for owner
//! - `owner_of(token_id) -> Address` - Owner of token
//! - `token_uri(token_id) -> String` - Metadata URI
//! - `transfer(to, token_id)` - Transfer token
//! - `approve(to, token_id)` - Approve transfer
//! - `get_approved(token_id) -> Address` - Get approved address
//! - `set_approval_for_all(operator, approved)` - Set operator approval
//! - `is_approved_for_all(owner, operator) -> bool` - Check operator
//! - `mint(to, token_uri) -> u64` - Mint new token
//! - `burn(token_id)` - Burn token

#![no_std]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use borsh::BorshDeserialize;
use synor_sdk::prelude::*;
use synor_sdk::{require, require_auth};

// ==================== Storage Keys ====================

mod keys {
    pub const NAME: &[u8] = b"syn721:name";
    pub const SYMBOL: &[u8] = b"syn721:symbol";
    pub const TOTAL_SUPPLY: &[u8] = b"syn721:total_supply";
    pub const NEXT_TOKEN_ID: &[u8] = b"syn721:next_token_id";
    pub const OWNER: &[u8] = b"syn721:owner";
    pub const OWNERS: &[u8] = b"syn721:owners";      // token_id -> Address
    pub const BALANCES: &[u8] = b"syn721:balances";  // Address -> count
    pub const TOKEN_URIS: &[u8] = b"syn721:uris";    // token_id -> String
    pub const APPROVALS: &[u8] = b"syn721:approvals"; // token_id -> Address
    pub const OPERATORS: &[u8] = b"syn721:operators"; // owner+operator -> bool
}

// ==================== Storage Helpers ====================

fn owners() -> storage::Map<[u8; 8], Address> {
    storage::Map::new(keys::OWNERS)
}

fn balances() -> storage::Map<[u8; 34], u64> {
    storage::Map::new(keys::BALANCES)
}

fn token_uris() -> storage::Map<[u8; 8], String> {
    storage::Map::new(keys::TOKEN_URIS)
}

fn approvals() -> storage::Map<[u8; 8], Address> {
    storage::Map::new(keys::APPROVALS)
}

fn token_id_key(token_id: u64) -> [u8; 8] {
    token_id.to_le_bytes()
}

fn operator_key(owner: &Address, operator: &Address) -> Vec<u8> {
    let mut key = Vec::with_capacity(68);
    key.extend_from_slice(owner.as_bytes());
    key.extend_from_slice(operator.as_bytes());
    key
}

fn get_operator(owner: &Address, operator: &Address) -> bool {
    let key = operator_key(owner, operator);
    storage::get_with_suffix::<bool>(keys::OPERATORS, &key).unwrap_or(false)
}

fn set_operator(owner: &Address, operator: &Address, approved: bool) {
    let key = operator_key(owner, operator);
    storage::set_with_suffix(keys::OPERATORS, &key, &approved);
}

fn get_owner() -> Option<Address> {
    storage::get::<Address>(keys::OWNER)
}

// ==================== Events ====================

struct TransferNFT {
    from: Address,
    to: Address,
    token_id: u64,
}

impl synor_sdk::events::Event for TransferNFT {
    fn topics(&self) -> Vec<synor_sdk::events::Topic> {
        vec![
            synor_sdk::events::Topic::from_name("Transfer"),
            synor_sdk::events::Topic::from(&self.from),
            synor_sdk::events::Topic::from(&self.to),
            synor_sdk::events::Topic::from(self.token_id),
        ]
    }

    fn data(&self) -> Vec<u8> {
        Vec::new()
    }
}

struct ApprovalNFT {
    owner: Address,
    approved: Address,
    token_id: u64,
}

impl synor_sdk::events::Event for ApprovalNFT {
    fn topics(&self) -> Vec<synor_sdk::events::Topic> {
        vec![
            synor_sdk::events::Topic::from_name("Approval"),
            synor_sdk::events::Topic::from(&self.owner),
            synor_sdk::events::Topic::from(&self.approved),
            synor_sdk::events::Topic::from(self.token_id),
        ]
    }

    fn data(&self) -> Vec<u8> {
        Vec::new()
    }
}

struct ApprovalForAll {
    owner: Address,
    operator: Address,
    approved: bool,
}

impl synor_sdk::events::Event for ApprovalForAll {
    fn topics(&self) -> Vec<synor_sdk::events::Topic> {
        vec![
            synor_sdk::events::Topic::from_name("ApprovalForAll"),
            synor_sdk::events::Topic::from(&self.owner),
            synor_sdk::events::Topic::from(&self.operator),
        ]
    }

    fn data(&self) -> Vec<u8> {
        vec![if self.approved { 1 } else { 0 }]
    }
}

// ==================== Entry Points ====================

synor_sdk::entry_point!(init, call);

/// Initialize the NFT collection.
fn init(params: &[u8]) -> Result<()> {
    #[derive(BorshDeserialize)]
    struct InitParams {
        name: String,
        symbol: String,
    }

    let params = InitParams::try_from_slice(params)
        .map_err(|_| Error::invalid_args("Invalid init params"))?;

    // Store collection metadata
    storage::set(keys::NAME, &params.name);
    storage::set(keys::SYMBOL, &params.symbol);
    storage::set(keys::TOTAL_SUPPLY, &0u64);
    storage::set(keys::NEXT_TOKEN_ID, &1u64);

    // Set owner
    let owner = caller();
    storage::set(keys::OWNER, &owner);

    Ok(())
}

/// Handle contract calls.
fn call(selector: &[u8], params: &[u8]) -> Result<Vec<u8>> {
    // Method selectors (computed at runtime)
    let name_sel = synor_sdk::method_selector("name");
    let symbol_sel = synor_sdk::method_selector("symbol");
    let total_supply_sel = synor_sdk::method_selector("total_supply");
    let balance_of_sel = synor_sdk::method_selector("balance_of");
    let owner_of_sel = synor_sdk::method_selector("owner_of");
    let token_uri_sel = synor_sdk::method_selector("token_uri");
    let transfer_sel = synor_sdk::method_selector("transfer");
    let approve_sel = synor_sdk::method_selector("approve");
    let get_approved_sel = synor_sdk::method_selector("get_approved");
    let set_approval_for_all_sel = synor_sdk::method_selector("set_approval_for_all");
    let is_approved_for_all_sel = synor_sdk::method_selector("is_approved_for_all");
    let mint_sel = synor_sdk::method_selector("mint");
    let burn_sel = synor_sdk::method_selector("burn");
    let owner_sel = synor_sdk::method_selector("owner");
    let transfer_ownership_sel = synor_sdk::method_selector("transfer_ownership");

    match selector {
        s if s == name_sel => {
            let name: String = storage::get(keys::NAME).unwrap_or_default();
            Ok(borsh::to_vec(&name).unwrap())
        }

        s if s == symbol_sel => {
            let symbol: String = storage::get(keys::SYMBOL).unwrap_or_default();
            Ok(borsh::to_vec(&symbol).unwrap())
        }

        s if s == total_supply_sel => {
            let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0);
            Ok(borsh::to_vec(&supply).unwrap())
        }

        s if s == balance_of_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                owner: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected owner address"))?;
            let balance = balances().get_or_default(&args.owner.0);
            Ok(borsh::to_vec(&balance).unwrap())
        }

        s if s == owner_of_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                token_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected token_id"))?;
            let owner = owners()
                .get(&token_id_key(args.token_id))
                .ok_or(Error::not_found("Token does not exist"))?;
            Ok(borsh::to_vec(&owner).unwrap())
        }

        s if s == token_uri_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                token_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected token_id"))?;
            let uri = token_uris()
                .get(&token_id_key(args.token_id))
                .unwrap_or_default();
            Ok(borsh::to_vec(&uri).unwrap())
        }

        s if s == transfer_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                to: Address,
                token_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected to, token_id"))?;
            do_transfer(&caller(), &args.to, args.token_id)?;
            Ok(Vec::new())
        }

        s if s == approve_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                to: Address,
                token_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected to, token_id"))?;

            let token_owner = owners()
                .get(&token_id_key(args.token_id))
                .ok_or(Error::not_found("Token does not exist"))?;

            let sender = caller();
            require!(
                sender == token_owner || get_operator(&token_owner, &sender),
                Error::Unauthorized
            );

            approvals().set(&token_id_key(args.token_id), &args.to);

            emit(&ApprovalNFT {
                owner: token_owner,
                approved: args.to,
                token_id: args.token_id,
            });

            Ok(Vec::new())
        }

        s if s == get_approved_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                token_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected token_id"))?;
            let approved = approvals()
                .get(&token_id_key(args.token_id))
                .unwrap_or(Address::zero());
            Ok(borsh::to_vec(&approved).unwrap())
        }

        s if s == set_approval_for_all_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                operator: Address,
                approved: bool,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected operator, approved"))?;

            let owner = caller();
            set_operator(&owner, &args.operator, args.approved);

            emit(&ApprovalForAll {
                owner,
                operator: args.operator,
                approved: args.approved,
            });

            Ok(Vec::new())
        }

        s if s == is_approved_for_all_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                owner: Address,
                operator: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected owner, operator"))?;
            let approved = get_operator(&args.owner, &args.operator);
            Ok(borsh::to_vec(&approved).unwrap())
        }

        s if s == mint_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                to: Address,
                token_uri: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected to, token_uri"))?;

            // Only owner can mint
            let contract_owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(contract_owner);

            // Get next token ID
            let token_id: u64 = storage::get(keys::NEXT_TOKEN_ID).unwrap_or(1);
            storage::set(keys::NEXT_TOKEN_ID, &(token_id + 1));

            // Set ownership
            owners().set(&token_id_key(token_id), &args.to);

            // Store token URI
            token_uris().set(&token_id_key(token_id), &args.token_uri);

            // Update balance
            let balance = balances().get_or_default(&args.to.0);
            balances().set(&args.to.0, &(balance + 1));

            // Update total supply
            let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0);
            storage::set(keys::TOTAL_SUPPLY, &(supply + 1));

            emit(&TransferNFT {
                from: Address::zero(),
                to: args.to,
                token_id,
            });

            Ok(borsh::to_vec(&token_id).unwrap())
        }

        s if s == burn_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                token_id: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected token_id"))?;

            let sender = caller();
            let token_owner = owners()
                .get(&token_id_key(args.token_id))
                .ok_or(Error::not_found("Token does not exist"))?;

            // Must be owner or approved
            require!(
                is_approved_or_owner(&sender, &token_owner, args.token_id),
                Error::Unauthorized
            );

            // Remove ownership
            owners().delete(&token_id_key(args.token_id));
            token_uris().delete(&token_id_key(args.token_id));
            approvals().delete(&token_id_key(args.token_id));

            // Update balance
            let balance = balances().get_or_default(&token_owner.0);
            balances().set(&token_owner.0, &balance.saturating_sub(1));

            // Update total supply
            let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0);
            storage::set(keys::TOTAL_SUPPLY, &supply.saturating_sub(1));

            emit(&TransferNFT {
                from: token_owner,
                to: Address::zero(),
                token_id: args.token_id,
            });

            Ok(Vec::new())
        }

        s if s == owner_sel => {
            let owner = get_owner().unwrap_or(Address::zero());
            Ok(borsh::to_vec(&owner).unwrap())
        }

        s if s == transfer_ownership_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                new_owner: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected new_owner"))?;

            let current_owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(current_owner);

            storage::set(keys::OWNER, &args.new_owner);

            emit(&synor_sdk::events::OwnershipTransferred {
                previous_owner: current_owner,
                new_owner: args.new_owner,
            });

            Ok(Vec::new())
        }

        _ => Err(Error::InvalidMethod),
    }
}

// ==================== Internal Functions ====================

fn is_approved_or_owner(spender: &Address, owner: &Address, token_id: u64) -> bool {
    *spender == *owner
        || approvals().get(&token_id_key(token_id)).as_ref() == Some(spender)
        || get_operator(owner, spender)
}

fn do_transfer(from: &Address, to: &Address, token_id: u64) -> Result<()> {
    let token_owner = owners()
        .get(&token_id_key(token_id))
        .ok_or(Error::not_found("Token does not exist"))?;

    require!(
        is_approved_or_owner(from, &token_owner, token_id),
        Error::Unauthorized
    );

    require!(
        *to != Address::zero(),
        Error::invalid_args("Cannot transfer to zero address")
    );

    // Clear approval
    approvals().delete(&token_id_key(token_id));

    // Update owner
    owners().set(&token_id_key(token_id), to);

    // Update balances
    let from_balance = balances().get_or_default(&token_owner.0);
    balances().set(&token_owner.0, &from_balance.saturating_sub(1));

    let to_balance = balances().get_or_default(&to.0);
    balances().set(&to.0, &(to_balance + 1));

    emit(&TransferNFT {
        from: token_owner,
        to: *to,
        token_id,
    });

    Ok(())
}
