import { AbiCoder, ethers } from 'ethers';
import crypto from 'crypto';
import fs from 'fs/promises';
import path from 'path';

class DeviceClient {
    private deviceKeys!: ethers.HDNodeWallet | ethers.Wallet;
    private serverEndpoint: string;
    private readonly KEY_FILE_PATH = '/home/relayup/dev/peaq/device-key.json';
    private readonly CONFIG_FILE_PATH = '/home/relayup/dev/00_saved.cfg';
    public machineAddress: string | null = null;
    public MachineStationFactoryContractAddress: string = '0xfbbC75F2182E0b3DC9559FAB1aaC115108D456D3';
    public didResolutionResult: string | null = null;
    public userWalletAddress: string | null = null;

    constructor() {
        this.serverEndpoint = 'http://134.122.52.153';
    }

    private async loadUserWalletAddress(): Promise<void> {
        const maxRetries = 15;
        const retryDelay = 3000; // 2 seconds in milliseconds
        const atecStatPath = '/home/relayup/dev/atec_stat.json';
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                const jsonData = await fs.readFile(atecStatPath, 'utf8');
                const atecStat = JSON.parse(jsonData);
                
                if (atecStat.fingerprint && atecStat.fingerprint !== 'UNKNOWN') {
                    this.userWalletAddress = atecStat.fingerprint;
                    console.log('Loaded user wallet address from fingerprint:', this.userWalletAddress);
                    return;
                } else {
                    console.warn('Fingerprint not found in atec_stat.json');
                    if (attempt === maxRetries) {
                        console.error('No fingerprint found after all attempts. Exiting program.');
                        process.exit(1);
                    }
                    await new Promise(resolve => setTimeout(resolve, retryDelay));
                }
            } catch (error) {
                console.error(`Attempt ${attempt}/${maxRetries} failed to read ${atecStatPath}`);
                if (attempt === maxRetries) {
                    console.error('Failed to load fingerprint file after all attempts. Exiting program.');
                    process.exit(1);
                }
                await new Promise(resolve => setTimeout(resolve, retryDelay));
            }
        }
    }

    private async checkConfigStatus(): Promise<void> {
        try {
            const configData = await fs.readFile(this.CONFIG_FILE_PATH, 'utf8');
            const lines = configData.split('\n');
            const configLine = lines.find(line => line.startsWith('10_Set='));
            
            if (!configLine || configLine.trim() === '10_Set=off') {
                console.log('Device registration is disabled (10_Set=off).');
                
                // Try to delete device_did.json if it exists
                try {
                    await fs.unlink('/home/relayup/dev/peaq/device_did.json');
                    console.log('Removed existing device_did.json file');
                } catch (err) {
                    // Ignore error if file doesn't exist
                    console.log('No device_did.json file to remove');
                }
                
                console.log('Exiting...');
                process.exit(0);
            }
            
            console.log('Device registration is enabled (10_Set=on)');
        } catch (error) {
            console.error('Error reading config file:', error);
            process.exit(1);
        }
    }

    async initialize(): Promise<void> {
        try {
            await new Promise(resolve => setTimeout(resolve, 30000));
            // Check config status first
            await this.checkConfigStatus();
            
            // Load user wallet address
            await this.loadUserWalletAddress();
            
            // Rest of the initialization...
            try {
                const machineAddress = await fs.readFile('/home/relayup/dev/peaq/machine-address.txt', 'utf8');
                if (machineAddress) {
                    this.machineAddress = machineAddress.trim();
                    console.log('Loaded existing machine address:', this.machineAddress);
                }
            } catch (error) {
                console.log('No existing machine address found');
            }
            
            const privateKey = await this.loadPrivateKey();
            if (privateKey) {
                this.deviceKeys = new ethers.Wallet(privateKey);
                console.log('Loaded existing device key');
            } else {
                await this.createNewKey();
                console.log('Created new device key');
            }
        } catch (error) {
            console.error('Initialization error:', error);
            throw error;
        }
    }

    private async loadPrivateKey(): Promise<string | null> {
        try {
            const data = await fs.readFile(this.KEY_FILE_PATH, 'utf8');
            const keyData = JSON.parse(data);
            
            // Validate the key format
            if (keyData.privateKey && keyData.privateKey.startsWith('0x')) {
                return keyData.privateKey;
            }
            return null;
        } catch (error) {
            // File doesn't exist or is invalid
            return null;
        }
    }

    private async createNewKey(): Promise<void> {
        // Create new random wallet
        this.deviceKeys = ethers.Wallet.createRandom();
        
        // Save to file
        const keyData = {
            privateKey: this.deviceKeys.privateKey,
            address: this.deviceKeys.address,
            created: new Date().toISOString()
        };

        await fs.writeFile(
            this.KEY_FILE_PATH,
            JSON.stringify(keyData, null, 2),
            'utf8'
        );
    }

    getDeviceAddress(): string {
        return this.deviceKeys.address;
    }

    getMachineAddress(): string {
        return this.machineAddress ?? '';
    }

    private generateFingerprint(): string {
        // Example fingerprint generation using device info
        const deviceInfo = {
            address: this.deviceKeys.address,
            timestamp: Date.now(),
            random: crypto.randomBytes(16).toString('hex')
        };
        return crypto.createHash('sha256')
            .update(JSON.stringify(deviceInfo))
            .digest('hex');
    }

    async requestRegistration(): Promise<{result: boolean, deviceAddress: string, machineAddress: string, didDocument: string}> {
        const fingerprint = this.generateFingerprint();
        
        try {
            const response = await fetch(`${this.serverEndpoint}/register-device`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    fingerprint,
                    publicKey: this.deviceKeys.signingKey.publicKey,
                    address: this.deviceKeys.address
                })
            });

            const data = await response.json();
            this.machineAddress = data.machineAddress;

            // Save machine address to a new file
            await fs.writeFile('/home/relayup/dev/peaq/machine-address.txt', this.machineAddress ?? '', 'utf8');
            console.log('Saved machine address to file');

            console.log(data);
            console.log(data.machineAddress);
            
            const getOwnerHash = await this.getOwnerHash(
                data.machineAddress,
            );

            console.log(getOwnerHash);

            const newNonce = this.getNonce();

            const didSignature = await this.signDIDCreation(
                data.machineAddress,
                newNonce,
                getOwnerHash
            );

            console.log(didSignature);

            // // Request DID creation
            const {result, didDocument: did} = await this.createDID(
                data.machineAddress,
                newNonce.toString(),
                didSignature,
                this.deviceKeys.address,
                this.userWalletAddress ?? ''
            );

            return {
                result: result,
                deviceAddress: this.deviceKeys.address,
                machineAddress: this.machineAddress ?? '',
                didDocument: did
            };
        } catch (error) {
            console.error('Registration failed:', error);
            throw error;
        }
    }

    private async signDIDCreation(machineAddress: string, nonce: BigInt, ownerHash: string) {
        // EIP-712 signature for DID creation
        const abiCoder = new AbiCoder();

        const addAttributeFunctionSignature =
          "addAttribute(address,bytes,bytes,uint32)";

        const createDidFunctionSelector = ethers
          .keccak256(ethers.toUtf8Bytes(addAttributeFunctionSignature))
          .substring(0, 10);

        const didName = `did:peaq:${this.userWalletAddress}#anyone`;
        const name = ethers.hexlify(ethers.toUtf8Bytes(didName));

        const didVal = ethers.hexlify(ethers.toUtf8Bytes(ownerHash));

        const validityFor = 0;

        const params = abiCoder.encode(
            ["address", "bytes", "bytes", "uint32"],
            [machineAddress, name, didVal, validityFor]
        );

        const calldata = params.replace("0x", createDidFunctionSelector);

          const domain = {
            name: "MachineSmartAccount", 
            version: "1", 
            chainId: 3338,
            verifyingContract: machineAddress,
          };
        
          const types = {
            Execute: [
              { name: "target", type: "address" },
              { name: "data", type: "bytes" },
              { name: "nonce", type: "uint256" },
            ],
          };

          const message = {
            target: "0x0000000000000000000000000000000000000800",
            data: calldata,
            nonce: nonce,
          };

        return await this.deviceKeys.signTypedData(domain, types, message);
    }

    getNonce(): BigInt {
        const now = BigInt(Date.now());
        const randomPart = BigInt(Math.floor(Math.random() * 1e18));
        return now * randomPart;
    }

    private async createDID(
        machineAddress: string,
        nonce: string,
        deviceSignature: string,
        deviceAddress: string,
        userAddress: string
    ): Promise<{result: boolean, didDocument: string}> {
        const response = await fetch(`${this.serverEndpoint}/create-did`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                machineAddress,
                nonce,
                deviceSignature,
                deviceAddress,
                userAddress
            })
        });

        return await response.json();
    }

    private async getOwnerHash(machineAddress: string) {
        const response = await fetch(`${this.serverEndpoint}/get-owner-hash`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                machineAddress,
                deviceAddress: this.deviceKeys.address
            })
        });

        return await response.json();
    }

    async checkDid(ethAddress: string, machineAddress: string): Promise<{result: boolean, didDocument: string}> {
        const response = await fetch(`${this.serverEndpoint}/check-did`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ ethAddress, machineAddress })
        });

        //TODO: Return DID resolution result
        return await response.json();
    }

    async createDidForExistingMachine(): Promise<{result: boolean, deviceAddress: string, machineAddress: string, didDocument: string}> {
        if (!this.machineAddress) {
            throw new Error('No machine address available');
        }

        try {
            const getOwnerHash = await this.getOwnerHash(this.machineAddress);
            console.log(getOwnerHash);

            const newNonce = this.getNonce();
            const didSignature = await this.signDIDCreation(
                this.machineAddress,
                newNonce,
                getOwnerHash
            );

            console.log(didSignature);

            const {result, didDocument: did} = await this.createDID(
                this.machineAddress,
                newNonce.toString(),
                didSignature,
                this.deviceKeys.address,
                this.userWalletAddress ?? ''
            );

            return {
                result: result,
                deviceAddress: this.deviceKeys.address,
                machineAddress: this.machineAddress,
                didDocument: did
            };
        } catch (error) {
            console.error('DID creation failed:', error);
            throw error;
        }
    }
}

async function main() {
    const device = new DeviceClient();
    await device.initialize();
    console.log(device.userWalletAddress);
    const didCheck = await device.checkDid(device.userWalletAddress ?? '', device.getMachineAddress());
    
    let did = 'UNKNOWN';
    if (didCheck.result) {
        did = didCheck.didDocument;
    } else {
        try {
            let result;
            // If we have a machine address and loaded keys, try to create DID directly
            if (device.getMachineAddress()) {
                console.log('Attempting to create DID with existing machine address...');
                result = await device.createDidForExistingMachine();
            } else {
                console.log('No existing machine address, performing full registration...');
                result = await device.requestRegistration();
            }
            
            console.log(result);
            if (result.result) {
                did = result.didDocument;
            }
        } catch (error) {
            console.error('Registration/DID creation failed:', error);
        }
    }

    console.log('Device registered:', did);
    
    // Write DID to file
    try {
        const didData = {
            name: `did:peaq:${device.userWalletAddress}#anyone`,
            id: did,
        };
        
        await fs.writeFile('/home/relayup/dev/peaq/device_did.json', JSON.stringify(didData, null, 2), 'utf8');
        console.log('DID data successfully written to device_did.json');
    } catch (error) {
        console.error('Error writing DID data to file:', error);
    }
}

if (import.meta.url === `file://${process.argv[1]}`) {
    main().catch(console.error);
} 
